diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /js/src/irregexp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'js/src/irregexp')
49 files changed, 26181 insertions, 0 deletions
diff --git a/js/src/irregexp/LICENSE.v8 b/js/src/irregexp/LICENSE.v8 new file mode 100644 index 0000000000..933718a9ef --- /dev/null +++ b/js/src/irregexp/LICENSE.v8 @@ -0,0 +1,26 @@ +Copyright 2006-2011, the V8 project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/js/src/irregexp/RegExpAPI.cpp b/js/src/irregexp/RegExpAPI.cpp new file mode 100644 index 0000000000..f1ba1fbc4b --- /dev/null +++ b/js/src/irregexp/RegExpAPI.cpp @@ -0,0 +1,922 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Copyright 2020 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/RegExpAPI.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Casting.h" + +#include "frontend/FrontendContext.h" // AutoReportFrontendContext +#include "frontend/TokenStream.h" +#include "gc/GC.h" +#include "gc/Zone.h" +#include "irregexp/imported/regexp-ast.h" +#include "irregexp/imported/regexp-bytecode-generator.h" +#include "irregexp/imported/regexp-compiler.h" +#include "irregexp/imported/regexp-interpreter.h" +#include "irregexp/imported/regexp-macro-assembler-arch.h" +#include "irregexp/imported/regexp-macro-assembler-tracer.h" +#include "irregexp/imported/regexp-parser.h" +#include "irregexp/imported/regexp-stack.h" +#include "irregexp/imported/regexp.h" +#include "irregexp/RegExpNativeMacroAssembler.h" +#include "irregexp/RegExpShim.h" +#include "jit/JitCommon.h" +#include "js/ColumnNumber.h" // JS::ColumnNumberOneOrigin, JS::ColumnNumberOffset +#include "js/friend/ErrorMessages.h" // JSMSG_* +#include "js/friend/StackLimits.h" // js::ReportOverRecursed +#include "util/StringBuffer.h" +#include "vm/MatchPairs.h" +#include "vm/PlainObject.h" +#include "vm/RegExpShared.h" + +namespace js { +namespace irregexp { + +using mozilla::AssertedCast; +using mozilla::Maybe; +using mozilla::Nothing; +using mozilla::PointerRangeSize; +using mozilla::Some; + +using frontend::DummyTokenStream; +using frontend::TokenStreamAnyChars; + +using v8::internal::DisallowGarbageCollection; +using v8::internal::HandleScope; +using v8::internal::InputOutputData; +using v8::internal::IrregexpInterpreter; +using v8::internal::NativeRegExpMacroAssembler; +using v8::internal::RegExpBytecodeGenerator; +using v8::internal::RegExpCapture; +using v8::internal::RegExpCompileData; +using v8::internal::RegExpCompiler; +using v8::internal::RegExpError; +using v8::internal::RegExpMacroAssembler; +using v8::internal::RegExpMacroAssemblerTracer; +using v8::internal::RegExpNode; +using v8::internal::RegExpParser; +using v8::internal::SMRegExpMacroAssembler; +using v8::internal::Zone; +using v8::internal::ZoneVector; + +using V8HandleString = v8::internal::Handle<v8::internal::String>; +using V8HandleRegExp = v8::internal::Handle<v8::internal::JSRegExp>; + +using namespace v8::internal::regexp_compiler_constants; + +static uint32_t ErrorNumber(RegExpError err) { + switch (err) { + case RegExpError::kNone: + return JSMSG_NOT_AN_ERROR; + case RegExpError::kStackOverflow: + return JSMSG_OVER_RECURSED; + case RegExpError::kAnalysisStackOverflow: + return JSMSG_OVER_RECURSED; + case RegExpError::kTooLarge: + return JSMSG_TOO_MANY_PARENS; + case RegExpError::kUnterminatedGroup: + return JSMSG_MISSING_PAREN; + case RegExpError::kUnmatchedParen: + return JSMSG_UNMATCHED_RIGHT_PAREN; + case RegExpError::kEscapeAtEndOfPattern: + return JSMSG_ESCAPE_AT_END_OF_REGEXP; + case RegExpError::kInvalidPropertyName: + return JSMSG_INVALID_PROPERTY_NAME; + case RegExpError::kInvalidEscape: + return JSMSG_INVALID_IDENTITY_ESCAPE; + case RegExpError::kInvalidDecimalEscape: + return JSMSG_INVALID_DECIMAL_ESCAPE; + case RegExpError::kInvalidUnicodeEscape: + return JSMSG_INVALID_UNICODE_ESCAPE; + case RegExpError::kNothingToRepeat: + return JSMSG_NOTHING_TO_REPEAT; + case RegExpError::kLoneQuantifierBrackets: + // Note: V8 reports the same error for both ']' and '}'. + return JSMSG_RAW_BRACKET_IN_REGEXP; + case RegExpError::kRangeOutOfOrder: + return JSMSG_NUMBERS_OUT_OF_ORDER; + case RegExpError::kIncompleteQuantifier: + return JSMSG_INCOMPLETE_QUANTIFIER; + case RegExpError::kInvalidQuantifier: + return JSMSG_INVALID_QUANTIFIER; + case RegExpError::kInvalidGroup: + return JSMSG_INVALID_GROUP; + case RegExpError::kMultipleFlagDashes: + case RegExpError::kRepeatedFlag: + case RegExpError::kInvalidFlagGroup: + // V8 contains experimental support for turning regexp flags on + // and off in the middle of a regular expression. Unless it + // becomes standardized, SM does not support this feature. + MOZ_CRASH("Mode modifiers not supported"); + case RegExpError::kNotLinear: + // V8 has an experimental non-backtracking engine. We do not + // support it yet. + MOZ_CRASH("Non-backtracking execution not supported"); + case RegExpError::kTooManyCaptures: + return JSMSG_TOO_MANY_PARENS; + case RegExpError::kInvalidCaptureGroupName: + return JSMSG_INVALID_CAPTURE_NAME; + case RegExpError::kDuplicateCaptureGroupName: + return JSMSG_DUPLICATE_CAPTURE_NAME; + case RegExpError::kInvalidNamedReference: + return JSMSG_INVALID_NAMED_REF; + case RegExpError::kInvalidNamedCaptureReference: + return JSMSG_INVALID_NAMED_CAPTURE_REF; + case RegExpError::kInvalidClassEscape: + return JSMSG_RANGE_WITH_CLASS_ESCAPE; + case RegExpError::kInvalidClassPropertyName: + return JSMSG_INVALID_CLASS_PROPERTY_NAME; + case RegExpError::kInvalidCharacterClass: + return JSMSG_RANGE_WITH_CLASS_ESCAPE; + case RegExpError::kUnterminatedCharacterClass: + return JSMSG_UNTERM_CLASS; + case RegExpError::kOutOfOrderCharacterClass: + return JSMSG_BAD_CLASS_RANGE; + + case RegExpError::kInvalidClassSetOperation: + return JSMSG_INVALID_CLASS_SET_OP; + case RegExpError::kInvalidCharacterInClass: + return JSMSG_INVALID_CHAR_IN_CLASS; + case RegExpError::kNegatedCharacterClassWithStrings: + return JSMSG_NEGATED_CLASS_WITH_STR; + + case RegExpError::NumErrors: + MOZ_CRASH("Unreachable"); + } + MOZ_CRASH("Unreachable"); +} + +Isolate* CreateIsolate(JSContext* cx) { + auto isolate = MakeUnique<Isolate>(cx); + if (!isolate || !isolate->init()) { + return nullptr; + } + return isolate.release(); +} + +void TraceIsolate(JSTracer* trc, Isolate* isolate) { isolate->trace(trc); } + +void DestroyIsolate(Isolate* isolate) { + MOZ_ASSERT(isolate->liveHandles() == 0); + MOZ_ASSERT(isolate->livePseudoHandles() == 0); + js_delete(isolate); +} + +size_t IsolateSizeOfIncludingThis(Isolate* isolate, + mozilla::MallocSizeOf mallocSizeOf) { + return isolate->sizeOfIncludingThis(mallocSizeOf); +} + +static JS::ColumnNumberOffset ComputeColumnOffset(const Latin1Char* begin, + const Latin1Char* end) { + return JS::ColumnNumberOffset( + AssertedCast<uint32_t>(PointerRangeSize(begin, end))); +} + +static JS::ColumnNumberOffset ComputeColumnOffset(const char16_t* begin, + const char16_t* end) { + return JS::ColumnNumberOffset( + AssertedCast<uint32_t>(unicode::CountUTF16CodeUnits(begin, end))); +} + +// This function is varargs purely so it can call ReportCompileErrorLatin1. +// We never call it with additional arguments. +template <typename CharT> +static void ReportSyntaxError(TokenStreamAnyChars& ts, + mozilla::Maybe<uint32_t> line, + mozilla::Maybe<JS::ColumnNumberOneOrigin> column, + RegExpCompileData& result, CharT* start, + size_t length, ...) { + MOZ_ASSERT(line.isSome() == column.isSome()); + + Maybe<gc::AutoSuppressGC> suppressGC; + if (JSContext* maybeCx = ts.context()->maybeCurrentJSContext()) { + suppressGC.emplace(maybeCx); + } + uint32_t errorNumber = ErrorNumber(result.error); + + if (errorNumber == JSMSG_OVER_RECURSED) { + ReportOverRecursed(ts.context()); + return; + } + + uint32_t offset = std::max(result.error_pos, 0); + MOZ_ASSERT(offset <= length); + + ErrorMetadata err; + + // Ordinarily this indicates whether line-of-context information can be + // added, but we entirely ignore that here because we create a + // a line of context based on the expression source. + uint32_t location = ts.currentToken().pos.begin; + if (ts.fillExceptingContext(&err, location)) { + JS::ColumnNumberOffset columnOffset = + ComputeColumnOffset(start, start + offset); + if (line.isSome()) { + // If this pattern is being checked by the frontend Parser instead + // of other API entry points like |new RegExp|, then the parser will + // have provided both a line and column pointing at the *beginning* + // of the RegExp literal inside the source text. + // We adjust the columnNumber to point to the actual syntax error + // inside the literal. + err.lineNumber = *line; + err.columnNumber = *column + columnOffset; + } else { + // Line breaks are not significant in pattern text in the same way as + // in source text, so act as though pattern text is a single line, then + // compute a column based on "code point" count (treating a lone + // surrogate as a "code point" in UTF-16). Gak. + err.lineNumber = 1; + err.columnNumber = JS::ColumnNumberOneOrigin() + columnOffset; + } + } + + // For most error reporting, the line of context derives from the token + // stream. So when location information doesn't come from the token + // stream, we can't give a line of context. But here the "line of context" + // can be (and is) derived from the pattern text, so we can provide it no + // matter if the location is derived from the caller. + + const CharT* windowStart = + (offset > ErrorMetadata::lineOfContextRadius) + ? start + (offset - ErrorMetadata::lineOfContextRadius) + : start; + + const CharT* windowEnd = + (length - offset > ErrorMetadata::lineOfContextRadius) + ? start + offset + ErrorMetadata::lineOfContextRadius + : start + length; + + size_t windowLength = PointerRangeSize(windowStart, windowEnd); + MOZ_ASSERT(windowLength <= ErrorMetadata::lineOfContextRadius * 2); + + // Create the windowed string, not including the potential line + // terminator. + StringBuffer windowBuf(ts.context()); + if (!windowBuf.append(windowStart, windowEnd)) { + return; + } + + // The line of context must be null-terminated, and StringBuffer doesn't + // make that happen unless we force it to. + if (!windowBuf.append('\0')) { + return; + } + + err.lineOfContext.reset(windowBuf.stealChars()); + if (!err.lineOfContext) { + return; + } + + err.lineLength = windowLength; + err.tokenOffset = offset - (windowStart - start); + + va_list args; + va_start(args, length); + ReportCompileErrorLatin1VA(ts.context(), std::move(err), nullptr, errorNumber, + &args); + va_end(args); +} + +static void ReportSyntaxError(TokenStreamAnyChars& ts, + RegExpCompileData& result, + Handle<JSAtom*> pattern) { + JS::AutoCheckCannotGC nogc_; + if (pattern->hasLatin1Chars()) { + ReportSyntaxError(ts, Nothing(), Nothing(), result, + pattern->latin1Chars(nogc_), pattern->length()); + } else { + ReportSyntaxError(ts, Nothing(), Nothing(), result, + pattern->twoByteChars(nogc_), pattern->length()); + } +} + +template <typename CharT> +static bool CheckPatternSyntaxImpl(js::LifoAlloc& alloc, + JS::NativeStackLimit stackLimit, + const CharT* input, uint32_t inputLength, + JS::RegExpFlags flags, + RegExpCompileData* result, + JS::AutoAssertNoGC& nogc) { + LifoAllocScope allocScope(&alloc); + Zone zone(allocScope.alloc()); + + return RegExpParser::VerifyRegExpSyntax(&zone, stackLimit, input, inputLength, + flags, result, nogc); +} + +bool CheckPatternSyntax(js::LifoAlloc& alloc, JS::NativeStackLimit stackLimit, + TokenStreamAnyChars& ts, + const mozilla::Range<const char16_t> chars, + JS::RegExpFlags flags, mozilla::Maybe<uint32_t> line, + mozilla::Maybe<JS::ColumnNumberOneOrigin> column) { + RegExpCompileData result; + JS::AutoAssertNoGC nogc; + if (!CheckPatternSyntaxImpl(alloc, stackLimit, chars.begin().get(), + chars.length(), flags, &result, nogc)) { + ReportSyntaxError(ts, line, column, result, chars.begin().get(), + chars.length()); + return false; + } + return true; +} + +bool CheckPatternSyntax(JSContext* cx, JS::NativeStackLimit stackLimit, + TokenStreamAnyChars& ts, Handle<JSAtom*> pattern, + JS::RegExpFlags flags) { + RegExpCompileData result; + JS::AutoAssertNoGC nogc(cx); + if (pattern->hasLatin1Chars()) { + if (!CheckPatternSyntaxImpl(cx->tempLifoAlloc(), stackLimit, + pattern->latin1Chars(nogc), pattern->length(), + flags, &result, nogc)) { + ReportSyntaxError(ts, result, pattern); + return false; + } + return true; + } + if (!CheckPatternSyntaxImpl(cx->tempLifoAlloc(), stackLimit, + pattern->twoByteChars(nogc), pattern->length(), + flags, &result, nogc)) { + ReportSyntaxError(ts, result, pattern); + return false; + } + return true; +} + +// A regexp is a good candidate for Boyer-Moore if it has at least 3 +// times as many characters as it has unique characters. Note that +// table lookups in irregexp are done modulo tableSize (128). +template <typename CharT> +static bool HasFewDifferentCharacters(const CharT* chars, size_t length) { + const uint32_t tableSize = + v8::internal::NativeRegExpMacroAssembler::kTableSize; + bool character_found[tableSize] = {}; + uint32_t different = 0; + for (uint32_t i = 0; i < length; i++) { + uint32_t ch = chars[i] % tableSize; + if (!character_found[ch]) { + character_found[ch] = true; + different++; + // We declare a regexp low-alphabet if it has at least 3 times as many + // characters as it has different characters. + if (different * 3 > length) { + return false; + } + } + } + return true; +} + +// Identifies the sort of pattern where Boyer-Moore is faster than string search +static bool UseBoyerMoore(Handle<JSAtom*> pattern, JS::AutoAssertNoGC& nogc) { + size_t length = + std::min(size_t(kMaxLookaheadForBoyerMoore), pattern->length()); + if (length <= kPatternTooShortForBoyerMoore) { + return false; + } + + if (pattern->hasLatin1Chars()) { + return HasFewDifferentCharacters(pattern->latin1Chars(nogc), length); + } + MOZ_ASSERT(pattern->hasTwoByteChars()); + return HasFewDifferentCharacters(pattern->twoByteChars(nogc), length); +} + +// Sample character frequency information for use in Boyer-Moore. +static void SampleCharacters(Handle<JSLinearString*> sample_subject, + RegExpCompiler& compiler) { + static const int kSampleSize = 128; + int chars_sampled = 0; + + int length = sample_subject->length(); + + int half_way = (length - kSampleSize) / 2; + for (int i = std::max(0, half_way); i < length && chars_sampled < kSampleSize; + i++, chars_sampled++) { + compiler.frequency_collator()->CountCharacter( + sample_subject->latin1OrTwoByteChar(i)); + } +} + +// Recursively walking the AST for a deeply nested regexp (like +// `/(a(a(a(a(a(a(a(...(a)...))))))))/`) may overflow the stack while +// compiling. To avoid this, we use V8's implementation of the Visitor +// pattern to walk the AST first with an overly large stack frame. +class RegExpDepthCheck final : public v8::internal::RegExpVisitor { + public: + explicit RegExpDepthCheck(JSContext* cx) : cx_(cx) {} + + bool check(v8::internal::RegExpTree* root) { + return !!root->Accept(this, nullptr); + } + + // Leaf nodes with no children +#define LEAF_DEPTH(Kind) \ + void* Visit##Kind(v8::internal::RegExp##Kind* node, void*) override { \ + uint8_t padding[FRAME_PADDING]; \ + dummy_ = padding; /* Prevent padding from being optimized away.*/ \ + AutoCheckRecursionLimit recursion(cx_); \ + return (void*)recursion.checkDontReport(cx_); \ + } + + LEAF_DEPTH(Assertion) + LEAF_DEPTH(Atom) + LEAF_DEPTH(BackReference) + LEAF_DEPTH(ClassSetOperand) + LEAF_DEPTH(ClassRanges) + LEAF_DEPTH(Empty) + LEAF_DEPTH(Text) +#undef LEAF_DEPTH + + // Wrapper nodes with one child +#define WRAPPER_DEPTH(Kind) \ + void* Visit##Kind(v8::internal::RegExp##Kind* node, void*) override { \ + uint8_t padding[FRAME_PADDING]; \ + dummy_ = padding; /* Prevent padding from being optimized away.*/ \ + AutoCheckRecursionLimit recursion(cx_); \ + if (!recursion.checkDontReport(cx_)) { \ + return nullptr; \ + } \ + return node->body()->Accept(this, nullptr); \ + } + + WRAPPER_DEPTH(Capture) + WRAPPER_DEPTH(Group) + WRAPPER_DEPTH(Lookaround) + WRAPPER_DEPTH(Quantifier) +#undef WRAPPER_DEPTH + + void* VisitAlternative(v8::internal::RegExpAlternative* node, + void*) override { + uint8_t padding[FRAME_PADDING]; + dummy_ = padding; /* Prevent padding from being optimized away.*/ + AutoCheckRecursionLimit recursion(cx_); + if (!recursion.checkDontReport(cx_)) { + return nullptr; + } + for (auto* child : *node->nodes()) { + if (!child->Accept(this, nullptr)) { + return nullptr; + } + } + return (void*)true; + } + void* VisitDisjunction(v8::internal::RegExpDisjunction* node, + void*) override { + uint8_t padding[FRAME_PADDING]; + dummy_ = padding; /* Prevent padding from being optimized away.*/ + AutoCheckRecursionLimit recursion(cx_); + if (!recursion.checkDontReport(cx_)) { + return nullptr; + } + for (auto* child : *node->alternatives()) { + if (!child->Accept(this, nullptr)) { + return nullptr; + } + } + return (void*)true; + } + void* VisitClassSetExpression(v8::internal::RegExpClassSetExpression* node, + void*) override { + uint8_t padding[FRAME_PADDING]; + dummy_ = padding; /* Prevent padding from being optimized away.*/ + AutoCheckRecursionLimit recursion(cx_); + if (!recursion.checkDontReport(cx_)) { + return nullptr; + } + for (auto* child : *node->operands()) { + if (!child->Accept(this, nullptr)) { + return nullptr; + } + } + return (void*)true; + } + + private: + JSContext* cx_; + void* dummy_ = nullptr; + + // This size is picked to be comfortably larger than any + // RegExp*::ToNode stack frame. + static const size_t FRAME_PADDING = 256; +}; + +enum class AssembleResult { + Success, + TooLarge, + OutOfMemory, +}; + +[[nodiscard]] static AssembleResult Assemble( + JSContext* cx, RegExpCompiler* compiler, RegExpCompileData* data, + MutableHandleRegExpShared re, Handle<JSAtom*> pattern, Zone* zone, + bool useNativeCode, bool isLatin1) { + // Because we create a StackMacroAssembler, this function is not allowed + // to GC. If needed, we allocate and throw errors in the caller. + jit::TempAllocator temp(&cx->tempLifoAlloc()); + Maybe<jit::JitContext> jctx; + Maybe<js::jit::StackMacroAssembler> stack_masm; + UniquePtr<RegExpMacroAssembler> masm; + if (useNativeCode) { + NativeRegExpMacroAssembler::Mode mode = + isLatin1 ? NativeRegExpMacroAssembler::LATIN1 + : NativeRegExpMacroAssembler::UC16; + // If we are compiling native code, we need a macroassembler, + // which needs a jit context. + jctx.emplace(cx); + stack_masm.emplace(cx, temp); +#ifdef DEBUG + // It would be much preferable to use `class AutoCreatedBy` here, but we + // may be operating without an assembler at all if `useNativeCode` is + // `false`, so there's no place to put such a call. + stack_masm.ref().pushCreator("Assemble() in RegExpAPI.cpp"); +#endif + uint32_t num_capture_registers = re->pairCount() * 2; + masm = MakeUnique<SMRegExpMacroAssembler>(cx, stack_masm.ref(), zone, mode, + num_capture_registers); + } else { + masm = MakeUnique<RegExpBytecodeGenerator>(cx->isolate, zone); + } + if (!masm) { + ReportOutOfMemory(cx); + return AssembleResult::OutOfMemory; + } + + bool isLargePattern = + pattern->length() > v8::internal::RegExp::kRegExpTooLargeToOptimize; + masm->set_slow_safe(isLargePattern); + if (compiler->optimize()) { + compiler->set_optimize(!isLargePattern); + } + + // When matching a regexp with known maximum length that is anchored + // at the end, we may be able to skip the beginning of long input + // strings. This decision is made here because it depends on + // information in the AST that isn't replicated in the Node + // structure used inside the compiler. + bool is_start_anchored = data->tree->IsAnchoredAtStart(); + bool is_end_anchored = data->tree->IsAnchoredAtEnd(); + int max_length = data->tree->max_match(); + static const int kMaxBacksearchLimit = 1024; + if (is_end_anchored && !is_start_anchored && !re->sticky() && + max_length < kMaxBacksearchLimit) { + masm->SetCurrentPositionFromEnd(max_length); + } + + if (re->global()) { + RegExpMacroAssembler::GlobalMode mode = RegExpMacroAssembler::GLOBAL; + if (data->tree->min_match() > 0) { + mode = RegExpMacroAssembler::GLOBAL_NO_ZERO_LENGTH_CHECK; + } else if (re->unicode()) { + mode = RegExpMacroAssembler::GLOBAL_UNICODE; + } + masm->set_global_mode(mode); + } + + // The masm tracer works as a thin wrapper around another macroassembler. + RegExpMacroAssembler* masm_ptr = masm.get(); +#ifdef DEBUG + UniquePtr<RegExpMacroAssembler> tracer_masm; + if (jit::JitOptions.trace_regexp_assembler) { + tracer_masm = MakeUnique<RegExpMacroAssemblerTracer>(cx->isolate, masm_ptr); + masm_ptr = tracer_masm.get(); + } +#endif + + // Compile the regexp. + V8HandleString wrappedPattern(v8::internal::String(pattern), cx->isolate); + RegExpCompiler::CompilationResult result = compiler->Assemble( + cx->isolate, masm_ptr, data->node, data->capture_count, wrappedPattern); + + if (useNativeCode) { +#ifdef DEBUG + // See comment referencing `pushCreator` above. + stack_masm.ref().popCreator(); +#endif + } + + if (!result.Succeeded()) { + MOZ_ASSERT(result.error == RegExpError::kTooLarge); + return AssembleResult::TooLarge; + } + if (result.code->value().isUndefined()) { + // SMRegExpMacroAssembler::GetCode returns undefined on OOM. + MOZ_ASSERT(useNativeCode); + return AssembleResult::OutOfMemory; + } + + re->updateMaxRegisters(result.num_registers); + if (useNativeCode) { + // Transfer ownership of the tables from the macroassembler to the + // RegExpShared. + SMRegExpMacroAssembler::TableVector& tables = + static_cast<SMRegExpMacroAssembler*>(masm.get())->tables(); + for (uint32_t i = 0; i < tables.length(); i++) { + if (!re->addTable(std::move(tables[i]))) { + ReportOutOfMemory(cx); + return AssembleResult::OutOfMemory; + } + } + re->setJitCode(v8::internal::Code::cast(*result.code).inner(), isLatin1); + } else { + // Transfer ownership of the bytecode from the HandleScope to the + // RegExpShared. + ByteArray bytecode = + v8::internal::ByteArray::cast(*result.code).takeOwnership(cx->isolate); + uint32_t length = bytecode->length; + re->setByteCode(bytecode.release(), isLatin1); + js::AddCellMemory(re, length, MemoryUse::RegExpSharedBytecode); + } + + return AssembleResult::Success; +} + +struct RegExpCaptureIndexLess { + bool operator()(const RegExpCapture* lhs, const RegExpCapture* rhs) const { + return lhs->index() < rhs->index(); + } +}; + +bool InitializeNamedCaptures(JSContext* cx, HandleRegExpShared re, + ZoneVector<RegExpCapture*>* namedCaptures) { + // The irregexp parser returns named capture information in the form + // of a ZoneVector of RegExpCaptures nodes, each of which stores the + // capture name and the corresponding capture index. We create a + // template object with a property for each capture name, and store + // the capture indices as a heap-allocated array. + uint32_t numNamedCaptures = namedCaptures->size(); + + // Named captures are sorted by name (because the set is used to ensure + // name uniqueness). But the capture name map must be sorted by index. + std::sort(namedCaptures->begin(), namedCaptures->end(), + RegExpCaptureIndexLess{}); + + // Create a plain template object. + Rooted<js::PlainObject*> templateObject( + cx, js::NewPlainObjectWithProto(cx, nullptr, TenuredObject)); + if (!templateObject) { + return false; + } + + // Allocate the capture index array. + uint32_t arraySize = numNamedCaptures * sizeof(uint32_t); + UniquePtr<uint32_t[], JS::FreePolicy> captureIndices( + static_cast<uint32_t*>(js_malloc(arraySize))); + if (!captureIndices) { + js::ReportOutOfMemory(cx); + return false; + } + + // Initialize the properties of the template and populate the + // capture index array. + RootedId id(cx); + RootedValue dummyString(cx, StringValue(cx->runtime()->emptyString)); + for (uint32_t i = 0; i < numNamedCaptures; i++) { + RegExpCapture* capture = (*namedCaptures)[i]; + JSAtom* name = + js::AtomizeChars(cx, capture->name()->data(), capture->name()->size()); + if (!name) { + return false; + } + id = NameToId(name->asPropertyName()); + if (!NativeDefineDataProperty(cx, templateObject, id, dummyString, + JSPROP_ENUMERATE)) { + return false; + } + captureIndices[i] = capture->index(); + } + + RegExpShared::InitializeNamedCaptures( + cx, re, numNamedCaptures, templateObject, captureIndices.release()); + return true; +} + +bool CompilePattern(JSContext* cx, MutableHandleRegExpShared re, + Handle<JSLinearString*> input, + RegExpShared::CodeKind codeKind) { + Rooted<JSAtom*> pattern(cx, re->getSource()); + JS::RegExpFlags flags = re->getFlags(); + LifoAllocScope allocScope(&cx->tempLifoAlloc()); + HandleScope handleScope(cx->isolate); + Zone zone(allocScope.alloc()); + + RegExpCompileData data; + { + V8HandleString wrappedPattern(v8::internal::String(pattern), cx->isolate); + if (!RegExpParser::ParseRegExpFromHeapString( + cx->isolate, &zone, wrappedPattern, flags, &data)) { + AutoReportFrontendContext fc(cx); + JS::CompileOptions options(cx); + DummyTokenStream dummyTokenStream(&fc, options); + ReportSyntaxError(dummyTokenStream, data, pattern); + return false; + } + } + + // Avoid stack overflow while recursively walking the AST. + RegExpDepthCheck depthCheck(cx); + if (!depthCheck.check(data.tree)) { + JS_ReportErrorASCII(cx, "regexp too big"); + cx->reportResourceExhaustion(); + return false; + } + + if (re->kind() == RegExpShared::Kind::Unparsed) { + // This is the first time we have compiled this regexp. + // First, check to see if we should use simple string search + // with an atom. + if (!flags.ignoreCase() && !flags.sticky()) { + Rooted<JSAtom*> searchAtom(cx); + if (data.simple) { + // The parse-tree is a single atom that is equal to the pattern. + searchAtom = re->getSource(); + } else if (data.tree->IsAtom() && data.capture_count == 0) { + // The parse-tree is a single atom that is not equal to the pattern. + v8::internal::RegExpAtom* atom = data.tree->AsAtom(); + const char16_t* twoByteChars = atom->data().begin(); + searchAtom = AtomizeChars(cx, twoByteChars, atom->length()); + if (!searchAtom) { + return false; + } + } + JS::AutoAssertNoGC nogc(cx); + if (searchAtom && !UseBoyerMoore(searchAtom, nogc)) { + re->useAtomMatch(searchAtom); + return true; + } + } + if (data.named_captures) { + if (!InitializeNamedCaptures(cx, re, data.named_captures)) { + return false; + } + } + // All fallible initialization has succeeded, so we can change state. + // Add one to capture_count to account for the whole-match capture. + uint32_t pairCount = data.capture_count + 1; + re->useRegExpMatch(pairCount); + } + + MOZ_ASSERT(re->kind() == RegExpShared::Kind::RegExp); + + RegExpCompiler compiler(cx->isolate, &zone, data.capture_count, flags, + input->hasLatin1Chars()); + + bool isLatin1 = input->hasLatin1Chars(); + + SampleCharacters(input, compiler); + data.node = compiler.PreprocessRegExp(&data, flags, isLatin1); + data.error = AnalyzeRegExp(cx->isolate, isLatin1, flags, data.node); + if (data.error != RegExpError::kNone) { + MOZ_ASSERT(data.error == RegExpError::kAnalysisStackOverflow); + ReportOverRecursed(cx); + return false; + } + + bool useNativeCode = codeKind == RegExpShared::CodeKind::Jitcode; + MOZ_ASSERT_IF(useNativeCode, IsNativeRegExpEnabled()); + + switch (Assemble(cx, &compiler, &data, re, pattern, &zone, useNativeCode, + isLatin1)) { + case AssembleResult::TooLarge: + JS_ReportErrorASCII(cx, "regexp too big"); + cx->reportResourceExhaustion(); + return false; + case AssembleResult::OutOfMemory: + MOZ_ASSERT(cx->isThrowingOutOfMemory()); + return false; + case AssembleResult::Success: + break; + } + return true; +} + +template <typename CharT> +RegExpRunStatus ExecuteRaw(jit::JitCode* code, const CharT* chars, + size_t length, size_t startIndex, + VectorMatchPairs* matches) { + InputOutputData data(chars, chars + length, startIndex, matches); + + static_assert(static_cast<int32_t>(RegExpRunStatus::Error) == + v8::internal::RegExp::kInternalRegExpException); + static_assert(static_cast<int32_t>(RegExpRunStatus::Success) == + v8::internal::RegExp::kInternalRegExpSuccess); + static_assert(static_cast<int32_t>(RegExpRunStatus::Success_NotFound) == + v8::internal::RegExp::kInternalRegExpFailure); + + typedef int (*RegExpCodeSignature)(InputOutputData*); + auto function = reinterpret_cast<RegExpCodeSignature>(code->raw()); + { + JS::AutoSuppressGCAnalysis nogc; + return (RegExpRunStatus)CALL_GENERATED_1(function, &data); + } +} + +RegExpRunStatus Interpret(JSContext* cx, MutableHandleRegExpShared re, + Handle<JSLinearString*> input, size_t startIndex, + VectorMatchPairs* matches) { + MOZ_ASSERT(re->getByteCode(input->hasLatin1Chars())); + + HandleScope handleScope(cx->isolate); + V8HandleRegExp wrappedRegExp(v8::internal::JSRegExp(re), cx->isolate); + V8HandleString wrappedInput(v8::internal::String(input), cx->isolate); + + static_assert(static_cast<int32_t>(RegExpRunStatus::Error) == + v8::internal::RegExp::kInternalRegExpException); + static_assert(static_cast<int32_t>(RegExpRunStatus::Success) == + v8::internal::RegExp::kInternalRegExpSuccess); + static_assert(static_cast<int32_t>(RegExpRunStatus::Success_NotFound) == + v8::internal::RegExp::kInternalRegExpFailure); + + RegExpRunStatus status = + (RegExpRunStatus)IrregexpInterpreter::MatchForCallFromRuntime( + cx->isolate, wrappedRegExp, wrappedInput, matches->pairsRaw(), + uint32_t(matches->pairCount() * 2), uint32_t(startIndex)); + + MOZ_ASSERT(status == RegExpRunStatus::Error || + status == RegExpRunStatus::Success || + status == RegExpRunStatus::Success_NotFound); + + return status; +} + +RegExpRunStatus Execute(JSContext* cx, MutableHandleRegExpShared re, + Handle<JSLinearString*> input, size_t startIndex, + VectorMatchPairs* matches) { + bool latin1 = input->hasLatin1Chars(); + jit::JitCode* jitCode = re->getJitCode(latin1); + bool isCompiled = !!jitCode; + + // Reset the Irregexp backtrack stack if it grows during execution. + irregexp::RegExpStackScope stackScope(cx->isolate); + + if (isCompiled) { + JS::AutoCheckCannotGC nogc; + if (latin1) { + return ExecuteRaw(jitCode, input->latin1Chars(nogc), input->length(), + startIndex, matches); + } + return ExecuteRaw(jitCode, input->twoByteChars(nogc), input->length(), + startIndex, matches); + } + + return Interpret(cx, re, input, startIndex, matches); +} + +RegExpRunStatus ExecuteForFuzzing(JSContext* cx, Handle<JSAtom*> pattern, + Handle<JSLinearString*> input, + JS::RegExpFlags flags, size_t startIndex, + VectorMatchPairs* matches, + RegExpShared::CodeKind codeKind) { + RootedRegExpShared re(cx, cx->zone()->regExps().get(cx, pattern, flags)); + if (!RegExpShared::compileIfNecessary(cx, &re, input, codeKind)) { + return RegExpRunStatus::Error; + } + return RegExpShared::execute(cx, &re, input, startIndex, matches); +} + +bool GrowBacktrackStack(RegExpStack* regexp_stack) { + return SMRegExpMacroAssembler::GrowBacktrackStack(regexp_stack); +} + +uint32_t CaseInsensitiveCompareNonUnicode(const char16_t* substring1, + const char16_t* substring2, + size_t byteLength) { + return SMRegExpMacroAssembler::CaseInsensitiveCompareNonUnicode( + substring1, substring2, byteLength); +} + +uint32_t CaseInsensitiveCompareUnicode(const char16_t* substring1, + const char16_t* substring2, + size_t byteLength) { + return SMRegExpMacroAssembler::CaseInsensitiveCompareUnicode( + substring1, substring2, byteLength); +} + +bool IsCharacterInRangeArray(uint32_t c, ByteArrayData* ranges) { + return SMRegExpMacroAssembler::IsCharacterInRangeArray(c, ranges); +} + +#ifdef DEBUG +bool IsolateShouldSimulateInterrupt(Isolate* isolate) { + return isolate->shouldSimulateInterrupt_ != 0; +} + +void IsolateSetShouldSimulateInterrupt(Isolate* isolate) { + isolate->shouldSimulateInterrupt_ = 1; +} +void IsolateClearShouldSimulateInterrupt(Isolate* isolate) { + isolate->shouldSimulateInterrupt_ = 0; +} +#endif + +} // namespace irregexp +} // namespace js diff --git a/js/src/irregexp/RegExpAPI.h b/js/src/irregexp/RegExpAPI.h new file mode 100644 index 0000000000..1c32ada998 --- /dev/null +++ b/js/src/irregexp/RegExpAPI.h @@ -0,0 +1,97 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* This is the interface that the regexp engine exposes to SpiderMonkey. */ + +#ifndef regexp_RegExpAPI_h +#define regexp_RegExpAPI_h + +#include "mozilla/Maybe.h" +#include "mozilla/MemoryReporting.h" +#include "mozilla/Range.h" + +#include <stddef.h> +#include <stdint.h> + +#include "jstypes.h" + +#include "irregexp/RegExpTypes.h" +#include "js/ColumnNumber.h" // JS::ColumnNumberOneOigin +#include "js/Stack.h" // JS::NativeStackLimit +#include "vm/RegExpShared.h" + +struct JS_PUBLIC_API JSContext; +class JS_PUBLIC_API JSTracer; + +namespace JS { +class RegExpFlags; +} + +namespace v8::internal { +class RegExpStack; +} + +namespace js { + +class VectorMatchPairs; +class LifoAlloc; + +namespace frontend { +class TokenStreamAnyChars; +} + +namespace irregexp { + +Isolate* CreateIsolate(JSContext* cx); +void TraceIsolate(JSTracer* trc, Isolate* isolate); +void DestroyIsolate(Isolate* isolate); + +size_t IsolateSizeOfIncludingThis(Isolate* isolate, + mozilla::MallocSizeOf mallocSizeOf); + +bool CheckPatternSyntax( + js::LifoAlloc& alloc, JS::NativeStackLimit stackLimit, + frontend::TokenStreamAnyChars& ts, + const mozilla::Range<const char16_t> chars, JS::RegExpFlags flags, + mozilla::Maybe<uint32_t> line = mozilla::Nothing(), + mozilla::Maybe<JS::ColumnNumberOneOrigin> column = mozilla::Nothing()); +bool CheckPatternSyntax(JSContext* cx, JS::NativeStackLimit stackLimit, + frontend::TokenStreamAnyChars& ts, + Handle<JSAtom*> pattern, JS::RegExpFlags flags); + +bool CompilePattern(JSContext* cx, MutableHandleRegExpShared re, + Handle<JSLinearString*> input, + RegExpShared::CodeKind codeKind); + +RegExpRunStatus Execute(JSContext* cx, MutableHandleRegExpShared re, + Handle<JSLinearString*> input, size_t start, + VectorMatchPairs* matches); + +RegExpRunStatus ExecuteForFuzzing(JSContext* cx, Handle<JSAtom*> pattern, + Handle<JSLinearString*> input, + JS::RegExpFlags flags, size_t startIndex, + VectorMatchPairs* matches, + RegExpShared::CodeKind codeKind); + +bool GrowBacktrackStack(v8::internal::RegExpStack* regexp_stack); + +uint32_t CaseInsensitiveCompareNonUnicode(const char16_t* substring1, + const char16_t* substring2, + size_t byteLength); +uint32_t CaseInsensitiveCompareUnicode(const char16_t* substring1, + const char16_t* substring2, + size_t byteLength); +bool IsCharacterInRangeArray(uint32_t c, ByteArrayData* ranges); + +#ifdef DEBUG +bool IsolateShouldSimulateInterrupt(Isolate* isolate); +void IsolateSetShouldSimulateInterrupt(Isolate* isolate); +void IsolateClearShouldSimulateInterrupt(Isolate* isolate); +#endif +} // namespace irregexp +} // namespace js + +#endif /* regexp_RegExpAPI_h */ diff --git a/js/src/irregexp/RegExpNativeMacroAssembler.cpp b/js/src/irregexp/RegExpNativeMacroAssembler.cpp new file mode 100644 index 0000000000..2a8b1749c2 --- /dev/null +++ b/js/src/irregexp/RegExpNativeMacroAssembler.cpp @@ -0,0 +1,1407 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Copyright 2020 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-macro-assembler-arch.h" +#include "irregexp/imported/regexp-stack.h" +#include "irregexp/imported/special-case.h" +#include "jit/Linker.h" +#include "jit/PerfSpewer.h" +#include "vm/MatchPairs.h" +#include "vm/Realm.h" +#ifdef MOZ_VTUNE +# include "vtune/VTuneWrapper.h" +#endif + +#include "jit/ABIFunctionList-inl.h" +#include "jit/MacroAssembler-inl.h" + +namespace v8 { +namespace internal { + +using js::MatchPairs; +using js::jit::AbsoluteAddress; +using js::jit::Address; +using js::jit::AllocatableGeneralRegisterSet; +using js::jit::Assembler; +using js::jit::BaseIndex; +using js::jit::CodeLocationLabel; +using js::jit::GeneralRegisterBackwardIterator; +using js::jit::GeneralRegisterForwardIterator; +using js::jit::GeneralRegisterSet; +using js::jit::Imm32; +using js::jit::ImmPtr; +using js::jit::ImmWord; +using js::jit::JitCode; +using js::jit::Linker; +using js::jit::LiveGeneralRegisterSet; +using js::jit::Register; +using js::jit::Registers; +using js::jit::StackMacroAssembler; + +SMRegExpMacroAssembler::SMRegExpMacroAssembler(JSContext* cx, + StackMacroAssembler& masm, + Zone* zone, Mode mode, + uint32_t num_capture_registers) + : NativeRegExpMacroAssembler(cx->isolate.ref(), zone), + cx_(cx), + masm_(masm), + mode_(mode), + num_registers_(num_capture_registers), + num_capture_registers_(num_capture_registers) { + // Each capture has a start and an end register + MOZ_ASSERT(num_capture_registers_ % 2 == 0); + + AllocatableGeneralRegisterSet regs(GeneralRegisterSet::All()); + + input_end_pointer_ = regs.takeAny(); + current_character_ = regs.takeAny(); + current_position_ = regs.takeAny(); + backtrack_stack_pointer_ = regs.takeAny(); + temp0_ = regs.takeAny(); + temp1_ = regs.takeAny(); + if (!regs.empty()) { + // Not enough registers on x86. + temp2_ = regs.takeAny(); + } + savedRegisters_ = js::jit::SavedNonVolatileRegisters(regs); + + masm_.jump(&entry_label_); // We'll generate the entry code later + masm_.bind(&start_label_); // and continue from here. +} + +int SMRegExpMacroAssembler::stack_limit_slack() { + return RegExpStack::kStackLimitSlack; +} + +void SMRegExpMacroAssembler::AdvanceCurrentPosition(int by) { + if (by != 0) { + masm_.addPtr(Imm32(by * char_size()), current_position_); + } +} + +void SMRegExpMacroAssembler::AdvanceRegister(int reg, int by) { + MOZ_ASSERT(reg >= 0 && reg < num_registers_); + if (by != 0) { + masm_.addPtr(Imm32(by), register_location(reg)); + } +} + +void SMRegExpMacroAssembler::Backtrack() { +#ifdef DEBUG + js::jit::Label bailOut; + // Check for simulating interrupt + masm_.branch32(Assembler::NotEqual, + AbsoluteAddress(&cx_->isolate->shouldSimulateInterrupt_), + Imm32(0), &bailOut); +#endif + // Check for an interrupt. We have to restart from the beginning if we + // are interrupted, so we only check for urgent interrupts. + js::jit::Label noInterrupt; + masm_.branchTest32( + Assembler::Zero, AbsoluteAddress(cx_->addressOfInterruptBits()), + Imm32(uint32_t(js::InterruptReason::CallbackUrgent)), &noInterrupt); +#ifdef DEBUG + // bailing out if we have simulating interrupt flag set + masm_.bind(&bailOut); +#endif + masm_.movePtr(ImmWord(int32_t(js::RegExpRunStatus::Error)), temp0_); + masm_.jump(&exit_label_); + masm_.bind(&noInterrupt); + + // Pop code location from backtrack stack and jump to location. + Pop(temp0_); + masm_.jump(temp0_); +} + +void SMRegExpMacroAssembler::Bind(Label* label) { + masm_.bind(label->inner()); + if (label->patchOffset_.bound()) { + AddLabelPatch(label->patchOffset_, label->pos()); + } +} + +// Check if current_position + cp_offset is the input start +void SMRegExpMacroAssembler::CheckAtStartImpl(int cp_offset, Label* on_cond, + Assembler::Condition cond) { + Address addr(current_position_, cp_offset * char_size()); + masm_.computeEffectiveAddress(addr, temp0_); + + masm_.branchPtr(cond, inputStart(), temp0_, LabelOrBacktrack(on_cond)); +} + +void SMRegExpMacroAssembler::CheckAtStart(int cp_offset, Label* on_at_start) { + CheckAtStartImpl(cp_offset, on_at_start, Assembler::Equal); +} + +void SMRegExpMacroAssembler::CheckNotAtStart(int cp_offset, + Label* on_not_at_start) { + CheckAtStartImpl(cp_offset, on_not_at_start, Assembler::NotEqual); +} + +void SMRegExpMacroAssembler::CheckCharacterImpl(Imm32 c, Label* on_cond, + Assembler::Condition cond) { + masm_.branch32(cond, current_character_, c, LabelOrBacktrack(on_cond)); +} + +void SMRegExpMacroAssembler::CheckCharacter(uint32_t c, Label* on_equal) { + CheckCharacterImpl(Imm32(c), on_equal, Assembler::Equal); +} + +void SMRegExpMacroAssembler::CheckNotCharacter(uint32_t c, + Label* on_not_equal) { + CheckCharacterImpl(Imm32(c), on_not_equal, Assembler::NotEqual); +} + +void SMRegExpMacroAssembler::CheckCharacterGT(base::uc16 limit, + Label* on_greater) { + CheckCharacterImpl(Imm32(limit), on_greater, Assembler::GreaterThan); +} + +void SMRegExpMacroAssembler::CheckCharacterLT(base::uc16 limit, + Label* on_less) { + CheckCharacterImpl(Imm32(limit), on_less, Assembler::LessThan); +} + +// Bitwise-and the current character with mask and then check for a +// match with c. +void SMRegExpMacroAssembler::CheckCharacterAfterAndImpl(uint32_t c, + uint32_t mask, + Label* on_cond, + bool is_not) { + if (c == 0) { + Assembler::Condition cond = is_not ? Assembler::NonZero : Assembler::Zero; + masm_.branchTest32(cond, current_character_, Imm32(mask), + LabelOrBacktrack(on_cond)); + } else { + Assembler::Condition cond = is_not ? Assembler::NotEqual : Assembler::Equal; + masm_.move32(Imm32(mask), temp0_); + masm_.and32(current_character_, temp0_); + masm_.branch32(cond, temp0_, Imm32(c), LabelOrBacktrack(on_cond)); + } +} + +void SMRegExpMacroAssembler::CheckCharacterAfterAnd(uint32_t c, uint32_t mask, + Label* on_equal) { + CheckCharacterAfterAndImpl(c, mask, on_equal, /*is_not =*/false); +} + +void SMRegExpMacroAssembler::CheckNotCharacterAfterAnd(uint32_t c, + uint32_t mask, + Label* on_not_equal) { + CheckCharacterAfterAndImpl(c, mask, on_not_equal, /*is_not =*/true); +} + +// Subtract minus from the current character, then bitwise-and the +// result with mask, then check for a match with c. +void SMRegExpMacroAssembler::CheckNotCharacterAfterMinusAnd( + base::uc16 c, base::uc16 minus, base::uc16 mask, Label* on_not_equal) { + masm_.computeEffectiveAddress(Address(current_character_, -minus), temp0_); + if (c == 0) { + masm_.branchTest32(Assembler::NonZero, temp0_, Imm32(mask), + LabelOrBacktrack(on_not_equal)); + } else { + masm_.and32(Imm32(mask), temp0_); + masm_.branch32(Assembler::NotEqual, temp0_, Imm32(c), + LabelOrBacktrack(on_not_equal)); + } +} + +// If the current position matches the position stored on top of the backtrack +// stack, pops the backtrack stack and branches to the given label. +void SMRegExpMacroAssembler::CheckGreedyLoop(Label* on_equal) { + js::jit::Label fallthrough; + masm_.branchPtr(Assembler::NotEqual, Address(backtrack_stack_pointer_, 0), + current_position_, &fallthrough); + masm_.addPtr(Imm32(sizeof(void*)), backtrack_stack_pointer_); // Pop. + JumpOrBacktrack(on_equal); + masm_.bind(&fallthrough); +} + +void SMRegExpMacroAssembler::CheckCharacterInRangeImpl( + base::uc16 from, base::uc16 to, Label* on_cond, Assembler::Condition cond) { + // x is in [from,to] if unsigned(x - from) <= to - from + masm_.computeEffectiveAddress(Address(current_character_, -from), temp0_); + masm_.branch32(cond, temp0_, Imm32(to - from), LabelOrBacktrack(on_cond)); +} + +void SMRegExpMacroAssembler::CheckCharacterInRange(base::uc16 from, + base::uc16 to, + Label* on_in_range) { + CheckCharacterInRangeImpl(from, to, on_in_range, Assembler::BelowOrEqual); +} + +void SMRegExpMacroAssembler::CheckCharacterNotInRange(base::uc16 from, + base::uc16 to, + Label* on_not_in_range) { + CheckCharacterInRangeImpl(from, to, on_not_in_range, Assembler::Above); +} + +/* static */ +bool SMRegExpMacroAssembler::IsCharacterInRangeArray(uint32_t c, + ByteArrayData* ranges) { + js::AutoUnsafeCallWithABI unsafe; + MOZ_ASSERT(ranges->length % sizeof(uint16_t) == 0); + uint32_t length = ranges->length / sizeof(uint16_t); + MOZ_ASSERT(length > 0); + + // Fast paths. + if (c < ranges->getTyped<uint16_t>(0)) { + // |c| is lower than the start of the first range. + // It is not in the range array. + return false; + } + if (c >= ranges->getTyped<uint16_t>(length - 1)) { + // |c| is higher than the last entry. If the table contains an odd + // number of entries, the last range is open-ended, so |c| is in + // the range array iff |length| is odd. + return (length % 2) != 0; + } + + // |ranges| is stored as an interval list: an ordered list of + // starting points, where every even index marks the beginning of a + // range of characters that are included, and every odd index marks + // the beginning of a range of characters that are excluded. For + // example, the set [1,2,3,7,8,9] would be represented as the + // range array [1,4,7,10]. If |ranges| has an odd number of entries, + // the last included range is open-ended (so the set containing + // every character would be represented as [0]). + // + // Because of the symmetry between included and excluded ranges, we + // can do a binary search for the index in |ranges| with the value + // closest to but not exceeding |c|. If that index is even, |c| is + // in an included range. If that index is odd, |c| is in an excluded + // range. + uint32_t lower = 0; + uint32_t upper = length; + uint32_t mid = 0; + do { + mid = lower + (upper - lower) / 2; + const base::uc16 elem = ranges->getTyped<uint16_t>(mid); + if (c < elem) { + upper = mid; + } else if (c > elem) { + lower = mid + 1; + } else { + break; + } + } while (lower < upper); + uint32_t rangeIndex = c < ranges->getTyped<uint16_t>(mid) ? mid - 1 : mid; + + // Included ranges start at even indices and end at odd indices. + return rangeIndex % 2 == 0; +} + +void SMRegExpMacroAssembler::CallIsCharacterInRangeArray( + const ZoneList<CharacterRange>* ranges) { + Handle<ByteArray> rangeArray = GetOrAddRangeArray(ranges); + masm_.movePtr(ImmPtr(rangeArray->inner()), temp0_); + + // Save volatile regs. Temp regs don't need to be saved. + LiveGeneralRegisterSet volatileRegs(GeneralRegisterSet::Volatile()); + volatileRegs.takeUnchecked(temp0_); + volatileRegs.takeUnchecked(temp1_); + if (temp2_ != js::jit::InvalidReg) { + volatileRegs.takeUnchecked(temp2_); + } + masm_.PushRegsInMask(volatileRegs); + + using Fn = bool (*)(uint32_t, ByteArrayData*); + masm_.setupUnalignedABICall(temp1_); + masm_.passABIArg(current_character_); + masm_.passABIArg(temp0_); + + masm_.callWithABI<Fn, ::js::irregexp::IsCharacterInRangeArray>(); + masm_.storeCallBoolResult(temp1_); + masm_.PopRegsInMask(volatileRegs); + + // GetOrAddRangeArray caches previously seen range arrays to reduce + // memory usage, so this may not be the first time we've seen this + // range array. We only need to transfer ownership from the + // HandleScope to the |tables_| vector once. + PseudoHandle<ByteArrayData> rawRangeArray = + rangeArray->maybeTakeOwnership(isolate()); + if (rawRangeArray) { + AddTable(std::move(rawRangeArray)); + } +} + +bool SMRegExpMacroAssembler::CheckCharacterInRangeArray( + const ZoneList<CharacterRange>* ranges, Label* on_in_range) { + CallIsCharacterInRangeArray(ranges); + masm_.branchTest32(Assembler::NonZero, temp1_, temp1_, + LabelOrBacktrack(on_in_range)); + return true; +} + +bool SMRegExpMacroAssembler::CheckCharacterNotInRangeArray( + const ZoneList<CharacterRange>* ranges, Label* on_not_in_range) { + CallIsCharacterInRangeArray(ranges); + masm_.branchTest32(Assembler::Zero, temp1_, temp1_, + LabelOrBacktrack(on_not_in_range)); + return true; +} + +void SMRegExpMacroAssembler::CheckBitInTable(Handle<ByteArray> table, + Label* on_bit_set) { + // Claim ownership of the ByteArray from the current HandleScope. + // ByteArrays are allocated on the C++ heap and are (eventually) + // owned by the RegExpShared. + PseudoHandle<ByteArrayData> rawTable = table->takeOwnership(isolate()); + + masm_.movePtr(ImmPtr(rawTable->data()), temp0_); + + masm_.move32(Imm32(kTableMask), temp1_); + masm_.and32(current_character_, temp1_); + + masm_.load8ZeroExtend(BaseIndex(temp0_, temp1_, js::jit::TimesOne), temp0_); + masm_.branchTest32(Assembler::NonZero, temp0_, temp0_, + LabelOrBacktrack(on_bit_set)); + + // Transfer ownership of |rawTable| to the |tables_| vector. + AddTable(std::move(rawTable)); +} + +void SMRegExpMacroAssembler::CheckNotBackReferenceImpl(int start_reg, + bool read_backward, + bool unicode, + Label* on_no_match, + bool ignore_case) { + js::jit::Label fallthrough; + + // Captures are stored as a sequential pair of registers. + // Find the length of the back-referenced capture and load the + // capture's start index into current_character_. + masm_.loadPtr(register_location(start_reg), // index of start + current_character_); + masm_.loadPtr(register_location(start_reg + 1), temp0_); // index of end + masm_.subPtr(current_character_, temp0_); // length of capture + + // Capture registers are either both set or both cleared. + // If the capture length is zero, then the capture is either empty or cleared. + // Fall through in both cases. + masm_.branchPtr(Assembler::Equal, temp0_, ImmWord(0), &fallthrough); + + // Check that there are sufficient characters left in the input. + if (read_backward) { + // If start + len > current, there isn't enough room for a + // lookbehind backreference. + masm_.loadPtr(inputStart(), temp1_); + masm_.addPtr(temp0_, temp1_); + masm_.branchPtr(Assembler::GreaterThan, temp1_, current_position_, + LabelOrBacktrack(on_no_match)); + } else { + // current_position_ is the negative offset from the end. + // If current + len > 0, there isn't enough room for a backreference. + masm_.movePtr(current_position_, temp1_); + masm_.addPtr(temp0_, temp1_); + masm_.branchPtr(Assembler::GreaterThan, temp1_, ImmWord(0), + LabelOrBacktrack(on_no_match)); + } + + if (mode_ == UC16 && ignore_case) { + // We call a helper function for case-insensitive non-latin1 strings. + + // Save volatile regs. temp1_, temp2_, and current_character_ + // don't need to be saved. current_position_ needs to be saved + // even if it's non-volatile, because we modify it to use as an argument. + LiveGeneralRegisterSet volatileRegs(GeneralRegisterSet::Volatile()); + volatileRegs.addUnchecked(current_position_); + volatileRegs.takeUnchecked(temp1_); + if (temp2_ != js::jit::InvalidReg) { + volatileRegs.takeUnchecked(temp2_); + } + volatileRegs.takeUnchecked(current_character_); + masm_.PushRegsInMask(volatileRegs); + + // Parameters are + // Address captured - Address of captured substring's start. + // Address current - Address of current character position. + // size_t byte_length - length of capture (in bytes) + + // Compute |captured| + masm_.addPtr(input_end_pointer_, current_character_); + + // Compute |current| + masm_.addPtr(input_end_pointer_, current_position_); + if (read_backward) { + // Offset by length when matching backwards. + masm_.subPtr(temp0_, current_position_); + } + + using Fn = uint32_t (*)(const char16_t*, const char16_t*, size_t); + masm_.setupUnalignedABICall(temp1_); + masm_.passABIArg(current_character_); + masm_.passABIArg(current_position_); + masm_.passABIArg(temp0_); + + if (unicode) { + masm_.callWithABI<Fn, ::js::irregexp::CaseInsensitiveCompareUnicode>(); + } else { + masm_.callWithABI<Fn, ::js::irregexp::CaseInsensitiveCompareNonUnicode>(); + } + masm_.storeCallInt32Result(temp1_); + masm_.PopRegsInMask(volatileRegs); + masm_.branchTest32(Assembler::Zero, temp1_, temp1_, + LabelOrBacktrack(on_no_match)); + + // On success, advance position by length of capture + if (read_backward) { + masm_.subPtr(temp0_, current_position_); + } else { + masm_.addPtr(temp0_, current_position_); + } + + masm_.bind(&fallthrough); + return; + } + + // We will be modifying current_position_. Save it in case the match fails. + masm_.push(current_position_); + + // Compute start of capture string + masm_.addPtr(input_end_pointer_, current_character_); + + // Compute start of match string + masm_.addPtr(input_end_pointer_, current_position_); + if (read_backward) { + // Offset by length when matching backwards. + masm_.subPtr(temp0_, current_position_); + } + + // Compute end of match string + masm_.addPtr(current_position_, temp0_); + + Register nextCaptureChar = temp1_; + Register nextMatchChar = temp2_; + + if (temp2_ == js::jit::InvalidReg) { + masm_.push(backtrack_stack_pointer_); + nextMatchChar = backtrack_stack_pointer_; + } + + js::jit::Label success; + js::jit::Label fail; + js::jit::Label loop; + masm_.bind(&loop); + + // Load next character from each string. + if (mode_ == LATIN1) { + masm_.load8ZeroExtend(Address(current_character_, 0), nextCaptureChar); + masm_.load8ZeroExtend(Address(current_position_, 0), nextMatchChar); + } else { + masm_.load16ZeroExtend(Address(current_character_, 0), nextCaptureChar); + masm_.load16ZeroExtend(Address(current_position_, 0), nextMatchChar); + } + + if (ignore_case) { + MOZ_ASSERT(mode_ == LATIN1); + // Try exact match. + js::jit::Label loop_increment; + masm_.branch32(Assembler::Equal, nextCaptureChar, nextMatchChar, + &loop_increment); + + // Mismatch. Try case-insensitive match. + // Force the capture character to lower case (by setting bit 0x20) + // then check to see if it is a letter. + js::jit::Label convert_match; + masm_.or32(Imm32(0x20), nextCaptureChar); + + // Check if it is in [a,z]. + masm_.computeEffectiveAddress(Address(nextCaptureChar, -'a'), + nextMatchChar); + masm_.branch32(Assembler::BelowOrEqual, nextMatchChar, Imm32('z' - 'a'), + &convert_match); + // Check for values in range [224,254]. + // Exclude 247 (U+00F7 DIVISION SIGN). + masm_.sub32(Imm32(224 - 'a'), nextMatchChar); + masm_.branch32(Assembler::Above, nextMatchChar, Imm32(254 - 224), &fail); + masm_.branch32(Assembler::Equal, nextMatchChar, Imm32(247 - 224), &fail); + + // Capture character is lower case. Convert match character + // to lower case and compare. + masm_.bind(&convert_match); + masm_.load8ZeroExtend(Address(current_position_, 0), nextMatchChar); + masm_.or32(Imm32(0x20), nextMatchChar); + masm_.branch32(Assembler::NotEqual, nextCaptureChar, nextMatchChar, &fail); + + masm_.bind(&loop_increment); + } else { + // Fail if characters do not match. + masm_.branch32(Assembler::NotEqual, nextCaptureChar, nextMatchChar, &fail); + } + + // Increment pointers into match and capture strings. + masm_.addPtr(Imm32(char_size()), current_character_); + masm_.addPtr(Imm32(char_size()), current_position_); + + // Loop if we have not reached the end of the match string. + masm_.branchPtr(Assembler::Below, current_position_, temp0_, &loop); + masm_.jump(&success); + + // If we fail, restore current_position_ and branch. + masm_.bind(&fail); + if (temp2_ == js::jit::InvalidReg) { + // Restore backtrack_stack_pointer_ when it was used as a temp register. + masm_.pop(backtrack_stack_pointer_); + } + masm_.pop(current_position_); + JumpOrBacktrack(on_no_match); + + masm_.bind(&success); + + if (temp2_ == js::jit::InvalidReg) { + // Restore backtrack_stack_pointer_ when it was used as a temp register. + masm_.pop(backtrack_stack_pointer_); + } + // Drop saved value of current_position_ + masm_.addToStackPtr(Imm32(sizeof(uintptr_t))); + + // current_position_ is a pointer. Convert it back to an offset. + masm_.subPtr(input_end_pointer_, current_position_); + if (read_backward) { + // Subtract match length if we matched backward + masm_.addPtr(register_location(start_reg), current_position_); + masm_.subPtr(register_location(start_reg + 1), current_position_); + } + + masm_.bind(&fallthrough); +} + +// Branch if a back-reference does not match a previous capture. +void SMRegExpMacroAssembler::CheckNotBackReference(int start_reg, + bool read_backward, + Label* on_no_match) { + CheckNotBackReferenceImpl(start_reg, read_backward, /*unicode = */ false, + on_no_match, /*ignore_case = */ false); +} + +void SMRegExpMacroAssembler::CheckNotBackReferenceIgnoreCase( + int start_reg, bool read_backward, bool unicode, Label* on_no_match) { + CheckNotBackReferenceImpl(start_reg, read_backward, unicode, on_no_match, + /*ignore_case = */ true); +} + +// Checks whether the given offset from the current position is +// inside the input string. +void SMRegExpMacroAssembler::CheckPosition(int cp_offset, + Label* on_outside_input) { + // Note: current_position_ is a (negative) byte offset relative to + // the end of the input string. + if (cp_offset >= 0) { + // end + current + offset >= end + // <=> current + offset >= 0 + // <=> current >= -offset + masm_.branchPtr(Assembler::GreaterThanOrEqual, current_position_, + ImmWord(-cp_offset * char_size()), + LabelOrBacktrack(on_outside_input)); + } else { + // Compute offset position + masm_.computeEffectiveAddress( + Address(current_position_, cp_offset * char_size()), temp0_); + + // Compare to start of input. + masm_.branchPtr(Assembler::GreaterThan, inputStart(), temp0_, + LabelOrBacktrack(on_outside_input)); + } +} + +// This function attempts to generate special case code for character classes. +// Returns true if a special case is generated. +// Otherwise returns false and generates no code. +bool SMRegExpMacroAssembler::CheckSpecialCharacterClass( + StandardCharacterSet type, Label* on_no_match) { + js::jit::Label* no_match = LabelOrBacktrack(on_no_match); + + // Note: throughout this function, range checks (c in [min, max]) + // are implemented by an unsigned (c - min) <= (max - min) check. + switch (type) { + case StandardCharacterSet::kWhitespace: { + // Match space-characters + if (mode_ != LATIN1) { + return false; + } + js::jit::Label success; + // One byte space characters are ' ', '\t'..'\r', and '\u00a0' (NBSP). + + // Check ' ' + masm_.branch32(Assembler::Equal, current_character_, Imm32(' '), + &success); + + // Check '\t'..'\r' + masm_.computeEffectiveAddress(Address(current_character_, -'\t'), temp0_); + masm_.branch32(Assembler::BelowOrEqual, temp0_, Imm32('\r' - '\t'), + &success); + + // Check \u00a0. + masm_.branch32(Assembler::NotEqual, temp0_, Imm32(0x00a0 - '\t'), + no_match); + + masm_.bind(&success); + return true; + } + case StandardCharacterSet::kNotWhitespace: + // The emitted code for generic character classes is good enough. + return false; + case StandardCharacterSet::kDigit: + // Match latin1 digits ('0'-'9') + masm_.computeEffectiveAddress(Address(current_character_, -'0'), temp0_); + masm_.branch32(Assembler::Above, temp0_, Imm32('9' - '0'), no_match); + return true; + case StandardCharacterSet::kNotDigit: + // Match anything except latin1 digits ('0'-'9') + masm_.computeEffectiveAddress(Address(current_character_, -'0'), temp0_); + masm_.branch32(Assembler::BelowOrEqual, temp0_, Imm32('9' - '0'), + no_match); + return true; + case StandardCharacterSet::kNotLineTerminator: + // Match non-newlines. This excludes '\n' (0x0a), '\r' (0x0d), + // U+2028 LINE SEPARATOR, and U+2029 PARAGRAPH SEPARATOR. + // See https://tc39.es/ecma262/#prod-LineTerminator + + // To test for 0x0a and 0x0d efficiently, we XOR the input with 1. + // This converts 0x0a to 0x0b, and 0x0d to 0x0c, allowing us to + // test for the contiguous range 0x0b..0x0c. + masm_.move32(current_character_, temp0_); + masm_.xor32(Imm32(0x01), temp0_); + masm_.sub32(Imm32(0x0b), temp0_); + masm_.branch32(Assembler::BelowOrEqual, temp0_, Imm32(0x0c - 0x0b), + no_match); + + if (mode_ == UC16) { + // Compare original value to 0x2028 and 0x2029, using the already + // computed (current_char ^ 0x01 - 0x0b). I.e., check for + // 0x201d (0x2028 - 0x0b) or 0x201e. + masm_.sub32(Imm32(0x2028 - 0x0b), temp0_); + masm_.branch32(Assembler::BelowOrEqual, temp0_, Imm32(0x2029 - 0x2028), + no_match); + } + return true; + case StandardCharacterSet::kWord: + // \w matches the set of 63 characters defined in Runtime Semantics: + // WordCharacters. We use a static lookup table, which is defined in + // regexp-macro-assembler.cc. + // Note: if both Unicode and IgnoreCase are true, \w matches a + // larger set of characters. That case is handled elsewhere. + if (mode_ != LATIN1) { + masm_.branch32(Assembler::Above, current_character_, Imm32('z'), + no_match); + } + static_assert(arraysize(word_character_map) > unibrow::Latin1::kMaxChar); + masm_.movePtr(ImmPtr(word_character_map), temp0_); + masm_.load8ZeroExtend( + BaseIndex(temp0_, current_character_, js::jit::TimesOne), temp0_); + masm_.branchTest32(Assembler::Zero, temp0_, temp0_, no_match); + return true; + case StandardCharacterSet::kNotWord: { + // See 'w' above. + js::jit::Label done; + if (mode_ != LATIN1) { + masm_.branch32(Assembler::Above, current_character_, Imm32('z'), &done); + } + static_assert(arraysize(word_character_map) > unibrow::Latin1::kMaxChar); + masm_.movePtr(ImmPtr(word_character_map), temp0_); + masm_.load8ZeroExtend( + BaseIndex(temp0_, current_character_, js::jit::TimesOne), temp0_); + masm_.branchTest32(Assembler::NonZero, temp0_, temp0_, no_match); + if (mode_ != LATIN1) { + masm_.bind(&done); + } + return true; + } + //////////////////////////////////////////////////////////////////////// + // Non-standard classes (with no syntactic shorthand) used internally // + //////////////////////////////////////////////////////////////////////// + case StandardCharacterSet::kEverything: + // Match any character + return true; + case StandardCharacterSet::kLineTerminator: + // Match newlines. The opposite of '.'. See '.' above. + masm_.move32(current_character_, temp0_); + masm_.xor32(Imm32(0x01), temp0_); + masm_.sub32(Imm32(0x0b), temp0_); + if (mode_ == LATIN1) { + masm_.branch32(Assembler::Above, temp0_, Imm32(0x0c - 0x0b), no_match); + } else { + MOZ_ASSERT(mode_ == UC16); + js::jit::Label done; + masm_.branch32(Assembler::BelowOrEqual, temp0_, Imm32(0x0c - 0x0b), + &done); + + // Compare original value to 0x2028 and 0x2029, using the already + // computed (current_char ^ 0x01 - 0x0b). I.e., check for + // 0x201d (0x2028 - 0x0b) or 0x201e. + masm_.sub32(Imm32(0x2028 - 0x0b), temp0_); + masm_.branch32(Assembler::Above, temp0_, Imm32(0x2029 - 0x2028), + no_match); + masm_.bind(&done); + } + return true; + } + return false; +} + +void SMRegExpMacroAssembler::Fail() { + masm_.movePtr(ImmWord(int32_t(js::RegExpRunStatus::Success_NotFound)), + temp0_); + masm_.jump(&exit_label_); +} + +void SMRegExpMacroAssembler::GoTo(Label* to) { + masm_.jump(LabelOrBacktrack(to)); +} + +void SMRegExpMacroAssembler::IfRegisterGE(int reg, int comparand, + Label* if_ge) { + masm_.branchPtr(Assembler::GreaterThanOrEqual, register_location(reg), + ImmWord(comparand), LabelOrBacktrack(if_ge)); +} + +void SMRegExpMacroAssembler::IfRegisterLT(int reg, int comparand, + Label* if_lt) { + masm_.branchPtr(Assembler::LessThan, register_location(reg), + ImmWord(comparand), LabelOrBacktrack(if_lt)); +} + +void SMRegExpMacroAssembler::IfRegisterEqPos(int reg, Label* if_eq) { + masm_.branchPtr(Assembler::Equal, register_location(reg), current_position_, + LabelOrBacktrack(if_eq)); +} + +// This is a word-for-word identical copy of the V8 code, which is +// duplicated in at least nine different places in V8 (one per +// supported architecture) with no differences outside of comments and +// formatting. It should be hoisted into the superclass. Once that is +// done upstream, this version can be deleted. +void SMRegExpMacroAssembler::LoadCurrentCharacterImpl(int cp_offset, + Label* on_end_of_input, + bool check_bounds, + int characters, + int eats_at_least) { + // It's possible to preload a small number of characters when each success + // path requires a large number of characters, but not the reverse. + MOZ_ASSERT(eats_at_least >= characters); + MOZ_ASSERT(cp_offset < (1 << 30)); // Be sane! (And ensure negation works) + + if (check_bounds) { + if (cp_offset >= 0) { + CheckPosition(cp_offset + eats_at_least - 1, on_end_of_input); + } else { + CheckPosition(cp_offset, on_end_of_input); + } + } + LoadCurrentCharacterUnchecked(cp_offset, characters); +} + +// Load the character (or characters) at the specified offset from the +// current position. Zero-extend to 32 bits. +void SMRegExpMacroAssembler::LoadCurrentCharacterUnchecked(int cp_offset, + int characters) { + BaseIndex address(input_end_pointer_, current_position_, js::jit::TimesOne, + cp_offset * char_size()); + if (mode_ == LATIN1) { + if (characters == 4) { + masm_.load32(address, current_character_); + } else if (characters == 2) { + masm_.load16ZeroExtend(address, current_character_); + } else { + MOZ_ASSERT(characters == 1); + masm_.load8ZeroExtend(address, current_character_); + } + } else { + MOZ_ASSERT(mode_ == UC16); + if (characters == 2) { + masm_.load32(address, current_character_); + } else { + MOZ_ASSERT(characters == 1); + masm_.load16ZeroExtend(address, current_character_); + } + } +} + +void SMRegExpMacroAssembler::PopCurrentPosition() { Pop(current_position_); } + +void SMRegExpMacroAssembler::PopRegister(int register_index) { + Pop(temp0_); + masm_.storePtr(temp0_, register_location(register_index)); +} + +void SMRegExpMacroAssembler::PushBacktrack(Label* label) { + MOZ_ASSERT(!label->is_bound()); + MOZ_ASSERT(!label->patchOffset_.bound()); + label->patchOffset_ = masm_.movWithPatch(ImmPtr(nullptr), temp0_); + MOZ_ASSERT(label->patchOffset_.bound()); + + Push(temp0_); + + CheckBacktrackStackLimit(); +} + +void SMRegExpMacroAssembler::PushCurrentPosition() { Push(current_position_); } + +void SMRegExpMacroAssembler::PushRegister(int register_index, + StackCheckFlag check_stack_limit) { + masm_.loadPtr(register_location(register_index), temp0_); + Push(temp0_); + if (check_stack_limit) { + CheckBacktrackStackLimit(); + } +} + +void SMRegExpMacroAssembler::ReadCurrentPositionFromRegister(int reg) { + masm_.loadPtr(register_location(reg), current_position_); +} + +void SMRegExpMacroAssembler::WriteCurrentPositionToRegister(int reg, + int cp_offset) { + if (cp_offset == 0) { + masm_.storePtr(current_position_, register_location(reg)); + } else { + Address addr(current_position_, cp_offset * char_size()); + masm_.computeEffectiveAddress(addr, temp0_); + masm_.storePtr(temp0_, register_location(reg)); + } +} + +// Note: The backtrack stack pointer is stored in a register as an +// offset from the stack top, not as a bare pointer, so that it is not +// corrupted if the backtrack stack grows (and therefore moves). +void SMRegExpMacroAssembler::ReadStackPointerFromRegister(int reg) { + masm_.loadPtr(register_location(reg), backtrack_stack_pointer_); + masm_.addPtr(backtrackStackBase(), backtrack_stack_pointer_); +} +void SMRegExpMacroAssembler::WriteStackPointerToRegister(int reg) { + masm_.movePtr(backtrack_stack_pointer_, temp0_); + masm_.subPtr(backtrackStackBase(), temp0_); + masm_.storePtr(temp0_, register_location(reg)); +} + +// When matching a regexp that is anchored at the end, this operation +// is used to try skipping the beginning of long strings. If the +// maximum length of a match is less than the length of the string, we +// can skip the initial len - max_len bytes. +void SMRegExpMacroAssembler::SetCurrentPositionFromEnd(int by) { + js::jit::Label after_position; + masm_.branchPtr(Assembler::GreaterThanOrEqual, current_position_, + ImmWord(-by * char_size()), &after_position); + masm_.movePtr(ImmWord(-by * char_size()), current_position_); + + // On RegExp code entry (where this operation is used), the character before + // the current position is expected to be already loaded. + // We have advanced the position, so it's safe to read backwards. + LoadCurrentCharacterUnchecked(-1, 1); + masm_.bind(&after_position); +} + +void SMRegExpMacroAssembler::SetRegister(int register_index, int to) { + MOZ_ASSERT(register_index >= num_capture_registers_); + masm_.storePtr(ImmWord(to), register_location(register_index)); +} + +// Returns true if a regexp match can be restarted (aka the regexp is global). +// The return value is not used anywhere, but we implement it to be safe. +bool SMRegExpMacroAssembler::Succeed() { + masm_.jump(&success_label_); + return global(); +} + +// Capture registers are initialized to input[-1] +void SMRegExpMacroAssembler::ClearRegisters(int reg_from, int reg_to) { + MOZ_ASSERT(reg_from <= reg_to); + masm_.loadPtr(inputStart(), temp0_); + masm_.subPtr(Imm32(char_size()), temp0_); + for (int reg = reg_from; reg <= reg_to; reg++) { + masm_.storePtr(temp0_, register_location(reg)); + } +} + +void SMRegExpMacroAssembler::Push(Register source) { + MOZ_ASSERT(source != backtrack_stack_pointer_); + + masm_.subPtr(Imm32(sizeof(void*)), backtrack_stack_pointer_); + masm_.storePtr(source, Address(backtrack_stack_pointer_, 0)); +} + +void SMRegExpMacroAssembler::Pop(Register target) { + MOZ_ASSERT(target != backtrack_stack_pointer_); + + masm_.loadPtr(Address(backtrack_stack_pointer_, 0), target); + masm_.addPtr(Imm32(sizeof(void*)), backtrack_stack_pointer_); +} + +void SMRegExpMacroAssembler::JumpOrBacktrack(Label* to) { + if (to) { + masm_.jump(to->inner()); + } else { + Backtrack(); + } +} + +// Generate a quick inline test for backtrack stack overflow. +// If the test fails, call an OOL handler to try growing the stack. +void SMRegExpMacroAssembler::CheckBacktrackStackLimit() { + js::jit::Label no_stack_overflow; + masm_.branchPtr( + Assembler::BelowOrEqual, + AbsoluteAddress(isolate()->regexp_stack()->limit_address_address()), + backtrack_stack_pointer_, &no_stack_overflow); + + masm_.call(&stack_overflow_label_); + + // Exit with an exception if the call failed + masm_.branchTest32(Assembler::Zero, temp0_, temp0_, + &exit_with_exception_label_); + + masm_.bind(&no_stack_overflow); +} + +// This is used to sneak an OOM through the V8 layer. +static Handle<HeapObject> DummyCode() { + return Handle<HeapObject>::fromHandleValue(JS::UndefinedHandleValue); +} + +// Finalize code. This is called last, so that we know how many +// registers we need. +Handle<HeapObject> SMRegExpMacroAssembler::GetCode(Handle<String> source) { + if (!cx_->zone()->ensureJitZoneExists(cx_)) { + return DummyCode(); + } + + masm_.bind(&entry_label_); + + createStackFrame(); + initFrameAndRegs(); + + masm_.jump(&start_label_); + + successHandler(); + exitHandler(); + backtrackHandler(); + stackOverflowHandler(); + + Linker linker(masm_); + JitCode* code = linker.newCode(cx_, js::jit::CodeKind::RegExp); + if (!code) { + return DummyCode(); + } + + for (LabelPatch& lp : labelPatches_) { + Assembler::PatchDataWithValueCheck(CodeLocationLabel(code, lp.patchOffset_), + ImmPtr(code->raw() + lp.labelOffset_), + ImmPtr(nullptr)); + } + + CollectPerfSpewerJitCodeProfile(code, "RegExp"); + +#ifdef MOZ_VTUNE + js::vtune::MarkStub(code, "RegExp"); +#endif + + return Handle<HeapObject>(JS::PrivateGCThingValue(code), isolate()); +} + +/* + * The stack will have the following structure: + * sp-> - FrameData + * - inputStart + * - backtrack stack base + * - matches + * - numMatches + * - Registers + * - Capture positions + * - Scratch registers + * --- frame alignment --- + * - Saved register area + * fp-> - Frame pointer + * - Return address + */ +void SMRegExpMacroAssembler::createStackFrame() { +#ifdef JS_CODEGEN_ARM64 + // ARM64 communicates stack address via SP, but uses a pseudo-sp (PSP) for + // addressing. The register we use for PSP may however also be used by + // calling code, and it is nonvolatile, so save it. Do this as a special + // case first because the generic save/restore code needs the PSP to be + // initialized already. + MOZ_ASSERT(js::jit::PseudoStackPointer64.Is(masm_.GetStackPointer64())); + masm_.Str(js::jit::PseudoStackPointer64, + vixl::MemOperand(js::jit::sp, -16, vixl::PreIndex)); + + // Initialize the PSP from the SP. + masm_.initPseudoStackPtr(); +#endif + + masm_.Push(js::jit::FramePointer); + masm_.moveStackPtrTo(js::jit::FramePointer); + + // Push non-volatile registers which might be modified by jitcode. + for (GeneralRegisterForwardIterator iter(savedRegisters_); iter.more(); + ++iter) { + masm_.Push(*iter); + } + + // The pointer to InputOutputData is passed as the first argument. + // On x86 we have to load it off the stack into temp0_. + // On other platforms it is already in a register. +#ifdef JS_CODEGEN_X86 + Address ioDataAddr(js::jit::FramePointer, 2 * sizeof(void*)); + masm_.loadPtr(ioDataAddr, temp0_); +#else + if (js::jit::IntArgReg0 != temp0_) { + masm_.movePtr(js::jit::IntArgReg0, temp0_); + } +#endif + + // Start a new stack frame. + size_t frameBytes = sizeof(FrameData) + num_registers_ * sizeof(void*); + frameSize_ = js::jit::StackDecrementForCall(js::jit::ABIStackAlignment, + masm_.framePushed(), frameBytes); + masm_.reserveStack(frameSize_); + masm_.checkStackAlignment(); + + // Check if we have space on the stack. Use the *NoInterrupt stack limit to + // avoid failing repeatedly when the regex code is called from Ion JIT code. + // (See bug 1208819) + js::jit::Label stack_ok; + AbsoluteAddress limit_addr(cx_->addressOfJitStackLimitNoInterrupt()); + masm_.branchStackPtrRhs(Assembler::Below, limit_addr, &stack_ok); + + // There is not enough space on the stack. Exit with an exception. + masm_.movePtr(ImmWord(int32_t(js::RegExpRunStatus::Error)), temp0_); + masm_.jump(&exit_label_); + + masm_.bind(&stack_ok); +} + +void SMRegExpMacroAssembler::initFrameAndRegs() { + // At this point, an uninitialized stack frame has been created, + // and the address of the InputOutputData is in temp0_. + Register ioDataReg = temp0_; + + Register matchesReg = temp1_; + masm_.loadPtr(Address(ioDataReg, offsetof(InputOutputData, matches)), + matchesReg); + + // Initialize output registers + // Use |backtrack_stack_pointer_| as an additional temp register. This is safe + // because we haven't yet written any data to |backtrack_stack_pointer_|. + Register extraTemp = backtrack_stack_pointer_; + + masm_.loadPtr(Address(matchesReg, MatchPairs::offsetOfPairs()), extraTemp); + masm_.storePtr(extraTemp, matches()); + masm_.load32(Address(matchesReg, MatchPairs::offsetOfPairCount()), extraTemp); + masm_.store32(extraTemp, numMatches()); + +#ifdef DEBUG + // Bounds-check numMatches. + js::jit::Label enoughRegisters; + masm_.branchPtr(Assembler::GreaterThanOrEqual, extraTemp, + ImmWord(num_capture_registers_ / 2), &enoughRegisters); + masm_.assumeUnreachable("Not enough output pairs for RegExp"); + masm_.bind(&enoughRegisters); +#endif + + // Load input start pointer. + masm_.loadPtr(Address(ioDataReg, offsetof(InputOutputData, inputStart)), + current_position_); + + // Load input end pointer + masm_.loadPtr(Address(ioDataReg, offsetof(InputOutputData, inputEnd)), + input_end_pointer_); + + // Set up input position to be negative offset from string end. + masm_.subPtr(input_end_pointer_, current_position_); + + // Store inputStart + masm_.storePtr(current_position_, inputStart()); + + // Load start index + Register startIndexReg = temp1_; + masm_.loadPtr(Address(ioDataReg, offsetof(InputOutputData, startIndex)), + startIndexReg); + masm_.computeEffectiveAddress( + BaseIndex(current_position_, startIndexReg, factor()), current_position_); + + // Initialize current_character_. + // Load newline if index is at start, or previous character otherwise. + js::jit::Label start_regexp; + js::jit::Label load_previous_character; + masm_.branchPtr(Assembler::NotEqual, startIndexReg, ImmWord(0), + &load_previous_character); + masm_.movePtr(ImmWord('\n'), current_character_); + masm_.jump(&start_regexp); + + masm_.bind(&load_previous_character); + LoadCurrentCharacterUnchecked(-1, 1); + masm_.bind(&start_regexp); + + // Initialize captured registers with inputStart - 1 + MOZ_ASSERT(num_capture_registers_ > 0); + Register inputStartMinusOneReg = temp0_; + masm_.loadPtr(inputStart(), inputStartMinusOneReg); + masm_.subPtr(Imm32(char_size()), inputStartMinusOneReg); + if (num_capture_registers_ > 8) { + masm_.movePtr(ImmWord(register_offset(0)), temp1_); + js::jit::Label init_loop; + masm_.bind(&init_loop); + masm_.storePtr(inputStartMinusOneReg, BaseIndex(masm_.getStackPointer(), + temp1_, js::jit::TimesOne)); + masm_.addPtr(ImmWord(sizeof(void*)), temp1_); + masm_.branchPtr(Assembler::LessThan, temp1_, + ImmWord(register_offset(num_capture_registers_)), + &init_loop); + } else { + // Unroll the loop + for (int i = 0; i < num_capture_registers_; i++) { + masm_.storePtr(inputStartMinusOneReg, register_location(i)); + } + } + + // Initialize backtrack stack pointer + masm_.loadPtr(AbsoluteAddress(ExternalReference::TopOfRegexpStack(isolate())), + backtrack_stack_pointer_); + masm_.storePtr(backtrack_stack_pointer_, backtrackStackBase()); +} + +// Called when we find a match. May not be generated if we can +// determine ahead of time that a regexp cannot match: for example, +// when compiling /\u1e9e/ for latin-1 inputs. +void SMRegExpMacroAssembler::successHandler() { + if (!success_label_.used()) { + return; + } + masm_.bind(&success_label_); + + // Copy captures to the MatchPairs pointed to by the InputOutputData. + // Captures are stored as positions, which are negative byte offsets + // from the end of the string. We must convert them to actual + // indices. + // + // Index: [ 0 ][ 1 ][ 2 ][ 3 ][ 4 ][ 5 ][END] + // Pos (1-byte): [-6 ][-5 ][-4 ][-3 ][-2 ][-1 ][ 0 ] // IS = -6 + // Pos (2-byte): [-12][-10][-8 ][-6 ][-4 ][-2 ][ 0 ] // IS = -12 + // + // To convert a position to an index, we subtract InputStart, and + // divide the result by char_size. + Register matchesReg = temp1_; + masm_.loadPtr(matches(), matchesReg); + + // Use |backtrack_stack_pointer_| as an additional temp register. This is safe + // because we don't read from |backtrack_stack_pointer_| after this point. + Register extraTemp = backtrack_stack_pointer_; + + Register inputStartReg = extraTemp; + masm_.loadPtr(inputStart(), inputStartReg); + + for (int i = 0; i < num_capture_registers_; i++) { + masm_.loadPtr(register_location(i), temp0_); + masm_.subPtr(inputStartReg, temp0_); + if (mode_ == UC16) { + masm_.rshiftPtrArithmetic(Imm32(1), temp0_); + } + masm_.store32(temp0_, Address(matchesReg, i * sizeof(int32_t))); + } + + masm_.movePtr(ImmWord(int32_t(js::RegExpRunStatus::Success)), temp0_); + // This falls through to the exit handler. +} + +void SMRegExpMacroAssembler::exitHandler() { + masm_.bind(&exit_label_); + + if (temp0_ != js::jit::ReturnReg) { + masm_.movePtr(temp0_, js::jit::ReturnReg); + } + + masm_.freeStack(frameSize_); + + // Restore registers which were saved on entry + for (GeneralRegisterBackwardIterator iter(savedRegisters_); iter.more(); + ++iter) { + masm_.Pop(*iter); + } + + masm_.Pop(js::jit::FramePointer); + +#ifdef JS_CODEGEN_ARM64 + // Now restore the value that was in the PSP register on entry, and return. + + // Obtain the correct SP from the PSP. + masm_.Mov(js::jit::sp, js::jit::PseudoStackPointer64); + + // Restore the saved value of the PSP register, this value is whatever the + // caller had saved in it, not any actual SP value, and it must not be + // overwritten subsequently. + masm_.Ldr(js::jit::PseudoStackPointer64, + vixl::MemOperand(js::jit::sp, 16, vixl::PostIndex)); + + // Perform a plain Ret(), as abiret() will move SP <- PSP and that is wrong. + masm_.Ret(vixl::lr); +#else + masm_.abiret(); +#endif + + if (exit_with_exception_label_.used()) { + masm_.bind(&exit_with_exception_label_); + + // Exit with an error result to signal thrown exception + masm_.movePtr(ImmWord(int32_t(js::RegExpRunStatus::Error)), temp0_); + masm_.jump(&exit_label_); + } +} + +void SMRegExpMacroAssembler::backtrackHandler() { + if (!backtrack_label_.used()) { + return; + } + masm_.bind(&backtrack_label_); + Backtrack(); +} + +void SMRegExpMacroAssembler::stackOverflowHandler() { + if (!stack_overflow_label_.used()) { + return; + } + + js::jit::AutoCreatedBy acb(masm_, + "SMRegExpMacroAssembler::stackOverflowHandler"); + + // Called if the backtrack-stack limit has been hit. + masm_.bind(&stack_overflow_label_); + + // Load argument + masm_.movePtr(ImmPtr(isolate()->regexp_stack()), temp1_); + + // Save registers before calling C function + LiveGeneralRegisterSet volatileRegs(GeneralRegisterSet::Volatile()); + +#ifdef JS_USE_LINK_REGISTER + masm_.pushReturnAddress(); +#endif + + // Adjust for the return address on the stack. + size_t frameOffset = sizeof(void*); + + volatileRegs.takeUnchecked(temp0_); + volatileRegs.takeUnchecked(temp1_); + masm_.PushRegsInMask(volatileRegs); + + using Fn = bool (*)(RegExpStack* regexp_stack); + masm_.setupUnalignedABICall(temp0_); + masm_.passABIArg(temp1_); + masm_.callWithABI<Fn, ::js::irregexp::GrowBacktrackStack>(); + masm_.storeCallBoolResult(temp0_); + + masm_.PopRegsInMask(volatileRegs); + + // If GrowBacktrackStack returned false, we have failed to grow the + // stack, and must exit with a stack-overflow exception. Do this in + // the caller so that the stack is adjusted by our return instruction. + js::jit::Label overflow_return; + masm_.branchTest32(Assembler::Zero, temp0_, temp0_, &overflow_return); + + // Otherwise, store the new backtrack stack base and recompute the new + // top of the stack. + Address bsbAddress(masm_.getStackPointer(), + offsetof(FrameData, backtrackStackBase) + frameOffset); + masm_.subPtr(bsbAddress, backtrack_stack_pointer_); + + masm_.loadPtr(AbsoluteAddress(ExternalReference::TopOfRegexpStack(isolate())), + temp1_); + masm_.storePtr(temp1_, bsbAddress); + masm_.addPtr(temp1_, backtrack_stack_pointer_); + + // Resume execution in calling code. + masm_.bind(&overflow_return); + masm_.ret(); +} + +// This is only used by tracing code. +// The return value doesn't matter. +RegExpMacroAssembler::IrregexpImplementation +SMRegExpMacroAssembler::Implementation() { + return kBytecodeImplementation; +} + +// Compare two strings in `/i` mode (ignoreCase, but not unicode). +/*static */ +uint32_t SMRegExpMacroAssembler::CaseInsensitiveCompareNonUnicode( + const char16_t* substring1, const char16_t* substring2, size_t byteLength) { + js::AutoUnsafeCallWithABI unsafe; + + MOZ_ASSERT(byteLength % sizeof(char16_t) == 0); + size_t length = byteLength / sizeof(char16_t); + + for (size_t i = 0; i < length; i++) { + char16_t c1 = substring1[i]; + char16_t c2 = substring2[i]; + if (c1 != c2) { +#ifdef JS_HAS_INTL_API + // Non-unicode regexps have weird case-folding rules. + c1 = RegExpCaseFolding::Canonicalize(c1); + c2 = RegExpCaseFolding::Canonicalize(c2); +#else + // If we aren't building with ICU, fall back to `/iu` mode. The only + // differences are in corner cases. + c1 = js::unicode::FoldCase(c1); + c2 = js::unicode::FoldCase(c2); +#endif + if (c1 != c2) { + return 0; + } + } + } + + return 1; +} + +// Compare two strings in `/iu` mode (ignoreCase and unicode). +/*static */ +uint32_t SMRegExpMacroAssembler::CaseInsensitiveCompareUnicode( + const char16_t* substring1, const char16_t* substring2, size_t byteLength) { + js::AutoUnsafeCallWithABI unsafe; + + MOZ_ASSERT(byteLength % sizeof(char16_t) == 0); + size_t length = byteLength / sizeof(char16_t); + + for (size_t i = 0; i < length; i++) { + char16_t c1 = substring1[i]; + char16_t c2 = substring2[i]; + if (c1 != c2) { + // Unicode regexps use the common and simple case-folding + // mappings of the Unicode Character Database. + c1 = js::unicode::FoldCase(c1); + c2 = js::unicode::FoldCase(c2); + if (c1 != c2) { + return 0; + } + } + } + + return 1; +} + +/* static */ +bool SMRegExpMacroAssembler::GrowBacktrackStack(RegExpStack* regexp_stack) { + js::AutoUnsafeCallWithABI unsafe; + size_t size = regexp_stack->memory_size(); + return !!regexp_stack->EnsureCapacity(size * 2); +} + +bool SMRegExpMacroAssembler::CanReadUnaligned() const { +#if defined(JS_CODEGEN_ARM) + return !js::jit::HasAlignmentFault(); +#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64) + return false; +#else + return true; +#endif +} + +} // namespace internal +} // namespace v8 diff --git a/js/src/irregexp/RegExpNativeMacroAssembler.h b/js/src/irregexp/RegExpNativeMacroAssembler.h new file mode 100644 index 0000000000..3c3acf40d4 --- /dev/null +++ b/js/src/irregexp/RegExpNativeMacroAssembler.h @@ -0,0 +1,308 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// Copyright 2020 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. + +// This file implements the NativeRegExpMacroAssembler interface for +// SpiderMonkey. It provides the same interface as each of V8's +// architecture-specific implementations. + +#ifndef RegexpMacroAssemblerArch_h +#define RegexpMacroAssemblerArch_h + +#include "irregexp/imported/regexp-macro-assembler.h" +#include "jit/MacroAssembler.h" + +namespace v8 { +namespace internal { + +struct FrameData { + // Character position at the start of the input, stored as a + // negative offset from the end of the string (input_end_pointer_). + size_t inputStart; + + // The backtrack_stack_pointer_ register points to the top of the stack. + // This points to the bottom of the backtrack stack. + void* backtrackStackBase; + + // Copy of the input MatchPairs. + int32_t* matches; // pointer to capture array + int32_t numMatches; // size of capture array +}; + +class SMRegExpMacroAssembler final : public NativeRegExpMacroAssembler { + public: + SMRegExpMacroAssembler(JSContext* cx, js::jit::StackMacroAssembler& masm, + Zone* zone, Mode mode, uint32_t num_capture_registers); + virtual ~SMRegExpMacroAssembler() = default; + + virtual int stack_limit_slack(); + virtual IrregexpImplementation Implementation(); + + virtual bool Succeed(); + virtual void Fail(); + + virtual void AdvanceCurrentPosition(int by); + virtual void PopCurrentPosition(); + virtual void PushCurrentPosition(); + virtual void SetCurrentPositionFromEnd(int by); + + virtual void Backtrack(); + virtual void Bind(Label* label); + virtual void GoTo(Label* label); + virtual void PushBacktrack(Label* label); + + virtual void CheckCharacter(uint32_t c, Label* on_equal); + virtual void CheckNotCharacter(uint32_t c, Label* on_not_equal); + virtual void CheckCharacterGT(base::uc16 limit, Label* on_greater); + virtual void CheckCharacterLT(base::uc16 limit, Label* on_less); + virtual void CheckCharacterAfterAnd(uint32_t c, uint32_t mask, + Label* on_equal); + virtual void CheckNotCharacterAfterAnd(uint32_t c, uint32_t mask, + Label* on_not_equal); + virtual void CheckNotCharacterAfterMinusAnd(base::uc16 c, base::uc16 minus, + base::uc16 mask, + Label* on_not_equal); + virtual void CheckGreedyLoop(Label* on_tos_equals_current_position); + virtual void CheckCharacterInRange(base::uc16 from, base::uc16 to, + Label* on_in_range); + virtual void CheckCharacterNotInRange(base::uc16 from, base::uc16 to, + Label* on_not_in_range); + virtual bool CheckCharacterInRangeArray( + const ZoneList<CharacterRange>* ranges, Label* on_in_range); + virtual bool CheckCharacterNotInRangeArray( + const ZoneList<CharacterRange>* ranges, Label* on_not_in_range); + virtual void CheckAtStart(int cp_offset, Label* on_at_start); + virtual void CheckNotAtStart(int cp_offset, Label* on_not_at_start); + virtual void CheckPosition(int cp_offset, Label* on_outside_input); + virtual void CheckBitInTable(Handle<ByteArray> table, Label* on_bit_set); + virtual bool CheckSpecialCharacterClass(StandardCharacterSet type, + Label* on_no_match); + virtual void CheckNotBackReference(int start_reg, bool read_backward, + Label* on_no_match); + virtual void CheckNotBackReferenceIgnoreCase(int start_reg, + bool read_backward, bool unicode, + Label* on_no_match); + + virtual void LoadCurrentCharacterImpl(int cp_offset, Label* on_end_of_input, + bool check_bounds, int characters, + int eats_at_least); + + virtual void AdvanceRegister(int reg, int by); + virtual void IfRegisterGE(int reg, int comparand, Label* if_ge); + virtual void IfRegisterLT(int reg, int comparand, Label* if_lt); + virtual void IfRegisterEqPos(int reg, Label* if_eq); + virtual void PopRegister(int register_index); + virtual void PushRegister(int register_index, + StackCheckFlag check_stack_limit); + virtual void ReadCurrentPositionFromRegister(int reg); + virtual void WriteCurrentPositionToRegister(int reg, int cp_offset); + virtual void ReadStackPointerFromRegister(int reg); + virtual void WriteStackPointerToRegister(int reg); + virtual void SetRegister(int register_index, int to); + virtual void ClearRegisters(int reg_from, int reg_to); + + virtual Handle<HeapObject> GetCode(Handle<String> source); + + virtual bool CanReadUnaligned() const; + + private: + size_t frameSize_ = 0; + + void createStackFrame(); + void initFrameAndRegs(); + void successHandler(); + void exitHandler(); + void backtrackHandler(); + void stackOverflowHandler(); + + // Push a register on the backtrack stack. + void Push(js::jit::Register value); + + // Pop a value from the backtrack stack. + void Pop(js::jit::Register target); + + void CheckAtStartImpl(int cp_offset, Label* on_cond, + js::jit::Assembler::Condition cond); + void CheckCharacterImpl(js::jit::Imm32 c, Label* on_cond, + js::jit::Assembler::Condition cond); + void CheckCharacterAfterAndImpl(uint32_t c, uint32_t and_with, Label* on_cond, + bool negate); + void CheckCharacterInRangeImpl(base::uc16 from, base::uc16 to, Label* on_cond, + js::jit::Assembler::Condition cond); + void CheckNotBackReferenceImpl(int start_reg, bool read_backward, + bool unicode, Label* on_no_match, + bool ignore_case); + void CallIsCharacterInRangeArray(const ZoneList<CharacterRange>* ranges); + + void LoadCurrentCharacterUnchecked(int cp_offset, int characters); + + void JumpOrBacktrack(Label* to); + + // MacroAssembler methods that take a Label can be called with a + // null label, which means that we should backtrack if we would jump + // to that label. This is a helper to avoid writing out the same + // logic a dozen times. + inline js::jit::Label* LabelOrBacktrack(Label* to) { + return to ? to->inner() : &backtrack_label_; + } + + void CheckBacktrackStackLimit(); + + public: + static bool GrowBacktrackStack(RegExpStack* regexp_stack); + + static uint32_t CaseInsensitiveCompareNonUnicode(const char16_t* substring1, + const char16_t* substring2, + size_t byteLength); + static uint32_t CaseInsensitiveCompareUnicode(const char16_t* substring1, + const char16_t* substring2, + size_t byteLength); + static bool IsCharacterInRangeArray(uint32_t c, ByteArrayData* ranges); + + private: + inline int char_size() { return static_cast<int>(mode_); } + inline js::jit::Scale factor() { + return mode_ == UC16 ? js::jit::TimesTwo : js::jit::TimesOne; + } + + js::jit::Address inputStart() { + return js::jit::Address(masm_.getStackPointer(), + offsetof(FrameData, inputStart)); + } + js::jit::Address backtrackStackBase() { + return js::jit::Address(masm_.getStackPointer(), + offsetof(FrameData, backtrackStackBase)); + } + js::jit::Address matches() { + return js::jit::Address(masm_.getStackPointer(), + offsetof(FrameData, matches)); + } + js::jit::Address numMatches() { + return js::jit::Address(masm_.getStackPointer(), + offsetof(FrameData, numMatches)); + } + + // The stack-pointer-relative location of a regexp register. + js::jit::Address register_location(int register_index) { + return js::jit::Address(masm_.getStackPointer(), + register_offset(register_index)); + } + + int32_t register_offset(int register_index) { + MOZ_ASSERT(register_index >= 0 && register_index <= kMaxRegister); + if (num_registers_ <= register_index) { + num_registers_ = register_index + 1; + } + static_assert(alignof(uintptr_t) <= alignof(FrameData)); + return sizeof(FrameData) + register_index * sizeof(uintptr_t*); + } + + JSContext* cx_; + js::jit::StackMacroAssembler& masm_; + + /* + * This assembler uses the following registers: + * + * - current_character_: + * Contains the character (or characters) currently being examined. + * Must be loaded using LoadCurrentCharacter before using any of the + * dispatch methods. After a matching pass for a global regexp, + * temporarily stores the index of capture start. + * - current_position_: + * Current position in input *as negative byte offset from end of string*. + * - input_end_pointer_: + * Points to byte after last character in the input. current_position_ is + * relative to this. + * - backtrack_stack_pointer_: + * Points to tip of the (heap-allocated) backtrack stack. The stack grows + * downward (like the native stack). + * - temp0_, temp1_, temp2_: + * Scratch registers. + * + * The native stack pointer is used to access arguments (InputOutputData), + * local variables (FrameData), and irregexp's internal virtual registers + * (see register_location). + */ + + js::jit::Register current_character_; + js::jit::Register current_position_; + js::jit::Register input_end_pointer_; + js::jit::Register backtrack_stack_pointer_; + js::jit::Register temp0_, temp1_, temp2_; + + // These labels are used in various API calls and bound (if used) in + // GetCode. If we abort in the middle of a compilation, as may + // happen if a regexp is too big, they may be used but not + // bound. + js::jit::NonAssertingLabel entry_label_; + js::jit::NonAssertingLabel start_label_; + js::jit::NonAssertingLabel backtrack_label_; + js::jit::NonAssertingLabel success_label_; + js::jit::NonAssertingLabel exit_label_; + js::jit::NonAssertingLabel stack_overflow_label_; + js::jit::NonAssertingLabel exit_with_exception_label_; + + // When we generate the code to push a backtrack label's address + // onto the backtrack stack, we don't know its final address. We + // have to patch it after linking. This is slightly delicate, as the + // Label itself (which is allocated on the stack) may not exist by + // the time we link. The approach is as follows: + // + // 1. When we push a label on the backtrack stack (PushBacktrack), + // we bind the label's patchOffset_ field to the offset within + // the code that should be overwritten. This works because each + // label is only pushed by a single instruction. + // + // 2. When we bind a label (Bind), we check to see if it has a + // bound patchOffset_. If it does, we create a LabelPatch mapping + // its patch offset to the offset of the label itself. + // + // 3. While linking the code, we walk the list of label patches + // and patch the code accordingly. + class LabelPatch { + public: + LabelPatch(js::jit::CodeOffset patchOffset, size_t labelOffset) + : patchOffset_(patchOffset), labelOffset_(labelOffset) {} + + js::jit::CodeOffset patchOffset_; + size_t labelOffset_ = 0; + }; + + js::Vector<LabelPatch, 4, js::SystemAllocPolicy> labelPatches_; + void AddLabelPatch(js::jit::CodeOffset patchOffset, size_t labelOffset) { + js::AutoEnterOOMUnsafeRegion oomUnsafe; + if (!labelPatches_.emplaceBack(patchOffset, labelOffset)) { + oomUnsafe.crash("Irregexp label patch"); + } + } + + Mode mode_; + int num_registers_; + int num_capture_registers_; + js::jit::LiveGeneralRegisterSet savedRegisters_; + + public: + using TableVector = + js::Vector<PseudoHandle<ByteArrayData>, 4, js::SystemAllocPolicy>; + TableVector& tables() { return tables_; } + + private: + TableVector tables_; + void AddTable(PseudoHandle<ByteArrayData> table) { + js::AutoEnterOOMUnsafeRegion oomUnsafe; + if (!tables_.append(std::move(table))) { + oomUnsafe.crash("Irregexp table append"); + } + } +}; + +} // namespace internal +} // namespace v8 + +#endif // RegexpMacroAssemblerArch_h diff --git a/js/src/irregexp/RegExpShim.cpp b/js/src/irregexp/RegExpShim.cpp new file mode 100644 index 0000000000..2b2c3cd4a0 --- /dev/null +++ b/js/src/irregexp/RegExpShim.cpp @@ -0,0 +1,297 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// 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/RegExpShim.h" + +#include "mozilla/MemoryReporting.h" + +#include <iostream> + +#include "irregexp/imported/regexp-macro-assembler.h" +#include "irregexp/imported/regexp-stack.h" + +#include "vm/NativeObject-inl.h" + +namespace v8 { +namespace internal { + +void PrintF(const char* format, ...) { + va_list arguments; + va_start(arguments, format); + vprintf(format, arguments); + va_end(arguments); +} + +void PrintF(FILE* out, const char* format, ...) { + va_list arguments; + va_start(arguments, format); + vfprintf(out, format, arguments); + va_end(arguments); +} + +StdoutStream::operator std::ostream&() const { return std::cerr; } + +template <typename T> +std::ostream& StdoutStream::operator<<(T t) { + return std::cerr << t; +} + +template std::ostream& StdoutStream::operator<<(char const* c); + +// Origin: +// https://github.com/v8/v8/blob/855591a54d160303349a5f0a32fab15825c708d1/src/utils/ostreams.cc#L120-L169 +// (This is a hand-simplified version.) +// Writes the given character to the output escaping everything outside +// of printable ASCII range. +std::ostream& operator<<(std::ostream& os, const AsUC16& c) { + base::uc16 v = c.value; + bool isPrint = 0x20 < v && v <= 0x7e; + char buf[10]; + const char* format = isPrint ? "%c" : (v <= 0xFF) ? "\\x%02x" : "\\u%04x"; + SprintfLiteral(buf, format, v); + return os << buf; +} +std::ostream& operator<<(std::ostream& os, const AsUC32& c) { + int32_t v = c.value; + if (v <= String::kMaxUtf16CodeUnit) { + return os << AsUC16(v); + } + char buf[13]; + SprintfLiteral(buf, "\\u{%06x}", v); + return os << buf; +} + +HandleScope::HandleScope(Isolate* isolate) : isolate_(isolate) { + isolate->openHandleScope(*this); +} + +HandleScope::~HandleScope() { + isolate_->closeHandleScope(level_, non_gc_level_); +} + +template <typename T> +Handle<T>::Handle(T object, Isolate* isolate) + : location_(isolate->getHandleLocation(object.value())) {} + +template Handle<ByteArray>::Handle(ByteArray b, Isolate* isolate); +template Handle<HeapObject>::Handle(const JS::Value& v, Isolate* isolate); +template Handle<JSRegExp>::Handle(JSRegExp re, Isolate* isolate); +template Handle<String>::Handle(String s, Isolate* isolate); + +template <typename T> +Handle<T>::Handle(const JS::Value& value, Isolate* isolate) + : location_(isolate->getHandleLocation(value)) { + T::cast(Object(value)); // Assert that value has the correct type. +} + +JS::Value* Isolate::getHandleLocation(const JS::Value& value) { + js::AutoEnterOOMUnsafeRegion oomUnsafe; + if (!handleArena_.Append(value)) { + oomUnsafe.crash("Irregexp handle allocation"); + } + return &handleArena_.GetLast(); +} + +void* Isolate::allocatePseudoHandle(size_t bytes) { + PseudoHandle<void> ptr; + ptr.reset(js_malloc(bytes)); + if (!ptr) { + return nullptr; + } + if (!uniquePtrArena_.Append(std::move(ptr))) { + return nullptr; + } + return uniquePtrArena_.GetLast().get(); +} + +template <typename T> +PseudoHandle<T> Isolate::takeOwnership(void* ptr) { + PseudoHandle<T> result = maybeTakeOwnership<T>(ptr); + MOZ_ASSERT(result); + return result; +} + +template <typename T> +PseudoHandle<T> Isolate::maybeTakeOwnership(void* ptr) { + for (auto iter = uniquePtrArena_.IterFromLast(); !iter.Done(); iter.Prev()) { + auto& entry = iter.Get(); + if (entry.get() == ptr) { + PseudoHandle<T> result; + result.reset(static_cast<T*>(entry.release())); + return result; + } + } + return PseudoHandle<T>(); +} + +PseudoHandle<ByteArrayData> ByteArray::maybeTakeOwnership(Isolate* isolate) { + PseudoHandle<ByteArrayData> result = + isolate->maybeTakeOwnership<ByteArrayData>(value().toPrivate()); + setValue(JS::PrivateValue(nullptr)); + return result; +} + +PseudoHandle<ByteArrayData> ByteArray::takeOwnership(Isolate* isolate) { + PseudoHandle<ByteArrayData> result = maybeTakeOwnership(isolate); + MOZ_ASSERT(result); + return result; +} + +void Isolate::trace(JSTracer* trc) { + js::gc::AssertRootMarkingPhase(trc); + + for (auto iter = handleArena_.Iter(); !iter.Done(); iter.Next()) { + auto& elem = iter.Get(); + JS::GCPolicy<JS::Value>::trace(trc, &elem, "Isolate handle arena"); + } +} + +size_t Isolate::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const { + size_t size = mallocSizeOf(this); + + size += mallocSizeOf(regexpStack_); + size += ExternalReference::SizeOfExcludingThis(mallocSizeOf, regexpStack_); + + size += handleArena_.SizeOfExcludingThis(mallocSizeOf); + size += uniquePtrArena_.SizeOfExcludingThis(mallocSizeOf); + return size; +} + +/*static*/ Handle<String> String::Flatten(Isolate* isolate, + Handle<String> string) { + if (string->IsFlat()) { + return string; + } + js::AutoEnterOOMUnsafeRegion oomUnsafe; + JSLinearString* linear = string->str()->ensureLinear(isolate->cx()); + if (!linear) { + oomUnsafe.crash("Irregexp String::Flatten"); + } + return Handle<String>(JS::StringValue(linear), isolate); +} + +// This is only used for trace messages printing the source pattern of +// a regular expression. We have to return a unique_ptr, but we don't +// care about the contents, so we return an empty null-terminated string. +std::unique_ptr<char[]> String::ToCString() { + js::AutoEnterOOMUnsafeRegion oomUnsafe; + + std::unique_ptr<char[]> ptr; + ptr.reset(static_cast<char*>(js_malloc(1))); + if (!ptr) { + oomUnsafe.crash("Irregexp String::ToCString"); + } + ptr[0] = '\0'; + + return ptr; +} + +bool Isolate::init() { + regexpStack_ = js_new<RegExpStack>(); + if (!regexpStack_) { + return false; + } + return true; +} + +Isolate::~Isolate() { + if (regexpStack_) { + js_delete(regexpStack_); + } +} + +/* static */ +const void* ExternalReference::TopOfRegexpStack(Isolate* isolate) { + return reinterpret_cast<const void*>( + isolate->regexp_stack()->memory_top_address_address()); +} + +/* static */ +size_t ExternalReference::SizeOfExcludingThis( + mozilla::MallocSizeOf mallocSizeOf, RegExpStack* regexpStack) { + if (regexpStack->thread_local_.owns_memory_) { + return mallocSizeOf(regexpStack->thread_local_.memory_); + } + return 0; +} + +Handle<ByteArray> Isolate::NewByteArray(int length, AllocationType alloc) { + MOZ_RELEASE_ASSERT(length >= 0); + + js::AutoEnterOOMUnsafeRegion oomUnsafe; + + size_t alloc_size = sizeof(uint32_t) + length; + ByteArrayData* data = + static_cast<ByteArrayData*>(allocatePseudoHandle(alloc_size)); + if (!data) { + oomUnsafe.crash("Irregexp NewByteArray"); + } + data->length = length; + + return Handle<ByteArray>(JS::PrivateValue(data), this); +} + +Handle<FixedArray> Isolate::NewFixedArray(int length) { + MOZ_RELEASE_ASSERT(length >= 0); + js::AutoEnterOOMUnsafeRegion oomUnsafe; + js::ArrayObject* array = js::NewDenseFullyAllocatedArray(cx(), length); + if (!array) { + oomUnsafe.crash("Irregexp NewFixedArray"); + } + array->ensureDenseInitializedLength(0, length); + return Handle<FixedArray>(JS::ObjectValue(*array), this); +} + +template <typename T> +Handle<FixedIntegerArray<T>> Isolate::NewFixedIntegerArray(uint32_t length) { + MOZ_RELEASE_ASSERT(length < std::numeric_limits<uint32_t>::max() / sizeof(T)); + js::AutoEnterOOMUnsafeRegion oomUnsafe; + + uint32_t rawLength = length * sizeof(T); + size_t allocSize = sizeof(ByteArrayData) + rawLength; + ByteArrayData* data = + static_cast<ByteArrayData*>(allocatePseudoHandle(allocSize)); + if (!data) { + oomUnsafe.crash("Irregexp NewFixedIntegerArray"); + } + data->length = rawLength; + + return Handle<FixedIntegerArray<T>>(JS::PrivateValue(data), this); +} + +template <typename T> +Handle<FixedIntegerArray<T>> FixedIntegerArray<T>::New(Isolate* isolate, + uint32_t length) { + return isolate->NewFixedIntegerArray<T>(length); +} + +template class FixedIntegerArray<uint16_t>; + +template <typename CharT> +Handle<String> Isolate::InternalizeString( + const base::Vector<const CharT>& str) { + js::AutoEnterOOMUnsafeRegion oomUnsafe; + JSAtom* atom = js::AtomizeChars(cx(), str.begin(), str.length()); + if (!atom) { + oomUnsafe.crash("Irregexp InternalizeString"); + } + return Handle<String>(JS::StringValue(atom), this); +} + +template Handle<String> Isolate::InternalizeString( + const base::Vector<const uint8_t>& str); +template Handle<String> Isolate::InternalizeString( + const base::Vector<const char16_t>& str); + +static_assert(JSRegExp::RegistersForCaptureCount(JSRegExp::kMaxCaptures) <= + RegExpMacroAssembler::kMaxRegisterCount); + +} // namespace internal +} // namespace v8 diff --git a/js/src/irregexp/RegExpShim.h b/js/src/irregexp/RegExpShim.h new file mode 100644 index 0000000000..3f85413421 --- /dev/null +++ b/js/src/irregexp/RegExpShim.h @@ -0,0 +1,1282 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// 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. + +#ifndef RegexpShim_h +#define RegexpShim_h + +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/Maybe.h" +#include "mozilla/SegmentedVector.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Types.h" + +#include <algorithm> +#include <cctype> +#include <iterator> + +#include "irregexp/RegExpTypes.h" +#include "irregexp/util/FlagsShim.h" +#include "irregexp/util/VectorShim.h" +#include "irregexp/util/ZoneShim.h" +#include "jit/JitCode.h" +#include "jit/Label.h" +#include "jit/shared/Assembler-shared.h" +#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit +#include "js/RegExpFlags.h" +#include "js/Value.h" +#include "threading/ExclusiveData.h" +#include "util/DifferentialTesting.h" +#include "vm/JSContext.h" +#include "vm/MutexIDs.h" +#include "vm/NativeObject.h" +#include "vm/RegExpShared.h" + +// Forward declaration of classes +namespace v8 { +namespace internal { + +class Heap; +class Isolate; +class RegExpMatchInfo; +class RegExpStack; + +template <typename T> +class Handle; + +} // namespace internal +} // namespace v8 + +#define V8_WARN_UNUSED_RESULT [[nodiscard]] +#define V8_EXPORT_PRIVATE +#define V8_FALLTHROUGH [[fallthrough]] +#define V8_NODISCARD [[nodiscard]] +#define V8_NOEXCEPT noexcept + +#define FATAL(x) MOZ_CRASH(x) +#define UNREACHABLE() MOZ_CRASH("unreachable code") +#define UNIMPLEMENTED() MOZ_CRASH("unimplemented code") +#define STATIC_ASSERT(exp) static_assert(exp, #exp) + +#define DCHECK MOZ_ASSERT +#define DCHECK_EQ(lhs, rhs) MOZ_ASSERT((lhs) == (rhs)) +#define DCHECK_NE(lhs, rhs) MOZ_ASSERT((lhs) != (rhs)) +#define DCHECK_GT(lhs, rhs) MOZ_ASSERT((lhs) > (rhs)) +#define DCHECK_GE(lhs, rhs) MOZ_ASSERT((lhs) >= (rhs)) +#define DCHECK_LT(lhs, rhs) MOZ_ASSERT((lhs) < (rhs)) +#define DCHECK_LE(lhs, rhs) MOZ_ASSERT((lhs) <= (rhs)) +#define DCHECK_NULL(val) MOZ_ASSERT((val) == nullptr) +#define DCHECK_NOT_NULL(val) MOZ_ASSERT((val) != nullptr) +#define DCHECK_IMPLIES(lhs, rhs) MOZ_ASSERT_IF(lhs, rhs) +#define CHECK MOZ_RELEASE_ASSERT +#define CHECK_EQ(lhs, rhs) MOZ_RELEASE_ASSERT((lhs) == (rhs)) +#define CHECK_LE(lhs, rhs) MOZ_RELEASE_ASSERT((lhs) <= (rhs)) +#define CHECK_GE(lhs, rhs) MOZ_RELEASE_ASSERT((lhs) >= (rhs)) +#define CONSTEXPR_DCHECK MOZ_ASSERT + +#define MemCopy memcpy + +// Origin: +// https://github.com/v8/v8/blob/855591a54d160303349a5f0a32fab15825c708d1/src/base/macros.h#L310-L319 +// ptrdiff_t is 't' according to the standard, but MSVC uses 'I'. +#ifdef _MSC_VER +# define V8PRIxPTRDIFF "Ix" +# define V8PRIdPTRDIFF "Id" +# define V8PRIuPTRDIFF "Iu" +#else +# define V8PRIxPTRDIFF "tx" +# define V8PRIdPTRDIFF "td" +# define V8PRIuPTRDIFF "tu" +#endif + +#define arraysize std::size + +// Explicitly declare the assignment operator as deleted. +#define DISALLOW_ASSIGN(TypeName) TypeName& operator=(const TypeName&) = delete + +// Explicitly declare the copy constructor and assignment operator as deleted. +// This also deletes the implicit move constructor and implicit move assignment +// operator, but still allows to manually define them. +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + DISALLOW_ASSIGN(TypeName) + +// Explicitly declare all implicit constructors as deleted, namely the +// default constructor, copy constructor and operator= functions. +// This is especially useful for classes containing only static methods. +#define DISALLOW_IMPLICIT_CONSTRUCTORS(TypeName) \ + TypeName() = delete; \ + DISALLOW_COPY_AND_ASSIGN(TypeName) + +namespace v8 { + +// Origin: +// https://github.com/v8/v8/blob/855591a54d160303349a5f0a32fab15825c708d1/src/base/macros.h#L364-L367 +template <typename T, typename U> +constexpr inline bool IsAligned(T value, U alignment) { + return (value & (alignment - 1)) == 0; +} + +using Address = uintptr_t; +static const Address kNullAddress = 0; + +inline uintptr_t GetCurrentStackPosition() { + return reinterpret_cast<uintptr_t>(__builtin_frame_address(0)); +} + +namespace base { + +// Latin1/UTF-16 constants +// Code-point values in Unicode 4.0 are 21 bits wide. +// Code units in UTF-16 are 16 bits wide. +using uc16 = char16_t; +using uc32 = uint32_t; + +constexpr int kUC16Size = sizeof(base::uc16); + +// Origin: +// https://github.com/v8/v8/blob/855591a54d160303349a5f0a32fab15825c708d1/src/base/macros.h#L247-L258 +// The USE(x, ...) template is used to silence C++ compiler warnings +// issued for (yet) unused variables (typically parameters). +// The arguments are guaranteed to be evaluated from left to right. +struct Use { + template <typename T> + Use(T&&) {} // NOLINT(runtime/explicit) +}; +#define USE(...) \ + do { \ + ::v8::base::Use unused_tmp_array_for_use_macro[]{__VA_ARGS__}; \ + (void)unused_tmp_array_for_use_macro; \ + } while (false) + +// Origin: +// https://github.com/v8/v8/blob/855591a54d160303349a5f0a32fab15825c708d1/src/base/safe_conversions.h#L35-L39 +// saturated_cast<> is analogous to static_cast<> for numeric types, except +// that the specified numeric conversion will saturate rather than overflow or +// underflow. +template <typename Dst, typename Src> +inline Dst saturated_cast(Src value); + +// This is the only specialization that is needed for regexp code. +// Instead of pulling in dozens of lines of template goo +// to derive it, I used the implementation from uint8_clamped in +// ArrayBufferObject.h. +template <> +inline uint8_t saturated_cast<uint8_t, int>(int x) { + return (x >= 0) ? ((x < 255) ? uint8_t(x) : 255) : 0; +} + +// Origin: +// https://github.com/v8/v8/blob/fc088cdaccadede84886eee881e67af9db53669a/src/base/bounds.h#L14-L28 +// Checks if value is in range [lower_limit, higher_limit] using a single +// branch. +template <typename T, typename U> +inline constexpr bool IsInRange(T value, U lower_limit, U higher_limit) { + using unsigned_T = typename std::make_unsigned<T>::type; + // Use static_cast to support enum classes. + return static_cast<unsigned_T>(static_cast<unsigned_T>(value) - + static_cast<unsigned_T>(lower_limit)) <= + static_cast<unsigned_T>(static_cast<unsigned_T>(higher_limit) - + static_cast<unsigned_T>(lower_limit)); +} + +#define LAZY_INSTANCE_INITIALIZER \ + {} + +template <typename T> +class LazyInstanceImpl { + public: + LazyInstanceImpl() : value_(js::mutexid::IrregexpLazyStatic) {} + + const T* Pointer() { + auto val = value_.lock(); + if (val->isNothing()) { + val->emplace(); + } + return val->ptr(); + } + + private: + js::ExclusiveData<mozilla::Maybe<T>> value_; +}; + +template <typename T> +class LazyInstance { + public: + using type = LazyInstanceImpl<T>; +}; + +// Origin: +// https://github.com/v8/v8/blob/855591a54d160303349a5f0a32fab15825c708d1/src/utils/utils.h#L40-L48 +// Returns the value (0 .. 15) of a hexadecimal character c. +// If c is not a legal hexadecimal character, returns a value < 0. +// Used in regexp-parser.cc +inline int HexValue(base::uc32 c) { + c -= '0'; + if (static_cast<unsigned>(c) <= 9) return c; + c = (c | 0x20) - ('a' - '0'); // detect 0x11..0x16 and 0x31..0x36. + if (static_cast<unsigned>(c) <= 5) return c + 10; + return -1; +} + +template <typename... Args> +[[nodiscard]] uint32_t hash_combine(uint32_t aHash, Args... aArgs) { + return mozilla::AddToHash(aHash, aArgs...); +} + +template <typename T> +class Optional { + mozilla::Maybe<T> inner_; + + public: + Optional() = default; + Optional(T t) { inner_.emplace(t); } + + bool has_value() const { return inner_.isSome(); } + const T& value() const { return inner_.ref(); } +}; + +namespace bits { + +inline uint64_t CountTrailingZeros(uint64_t value) { + return mozilla::CountTrailingZeroes64(value); +} + +inline size_t RoundUpToPowerOfTwo32(size_t value) { + return mozilla::RoundUpPow2(value); +} + +template <typename T> +constexpr bool IsPowerOfTwo(T value) { + return value > 0 && (value & (value - 1)) == 0; +} + +} // namespace bits +} // namespace base + +namespace unibrow { + +using uchar = unsigned int; + +// Origin: +// https://github.com/v8/v8/blob/1f1e4cdb04c75eab77adbecd5f5514ddc3eb56cf/src/strings/unicode.h#L133-L150 +class Latin1 { + public: + static const base::uc16 kMaxChar = 0xff; + + // Convert the character to Latin-1 case equivalent if possible. + static inline base::uc16 TryConvertToLatin1(base::uc16 c) { + // "GREEK CAPITAL LETTER MU" case maps to "MICRO SIGN". + // "GREEK SMALL LETTER MU" case maps to "MICRO SIGN". + if (c == 0x039C || c == 0x03BC) { + return 0xB5; + } + // "LATIN CAPITAL LETTER Y WITH DIAERESIS" case maps to "LATIN SMALL LETTER + // Y WITH DIAERESIS". + if (c == 0x0178) { + return 0xFF; + } + return c; + } +}; + +// Origin: +// https://github.com/v8/v8/blob/b4bfbce6f91fc2cc72178af42bb3172c5f5eaebb/src/strings/unicode.h#L99-L131 +class Utf16 { + public: + static inline bool IsLeadSurrogate(int code) { + return js::unicode::IsLeadSurrogate(code); + } + static inline bool IsTrailSurrogate(int code) { + return js::unicode::IsTrailSurrogate(code); + } + static inline base::uc16 LeadSurrogate(uint32_t char_code) { + return js::unicode::LeadSurrogate(char_code); + } + static inline base::uc16 TrailSurrogate(uint32_t char_code) { + return js::unicode::TrailSurrogate(char_code); + } + static inline uint32_t CombineSurrogatePair(char16_t lead, char16_t trail) { + return js::unicode::UTF16Decode(lead, trail); + } + static const uchar kMaxNonSurrogateCharCode = 0xffff; +}; + +#ifndef V8_INTL_SUPPORT + +// A cache used in case conversion. It caches the value for characters +// that either have no mapping or map to a single character independent +// of context. Characters that map to more than one character or that +// map differently depending on context are always looked up. +// Origin: +// https://github.com/v8/v8/blob/b4bfbce6f91fc2cc72178af42bb3172c5f5eaebb/src/strings/unicode.h#L64-L88 +template <class T, int size = 256> +class Mapping { + public: + inline Mapping() = default; + inline int get(uchar c, uchar n, uchar* result) { + CacheEntry entry = entries_[c & kMask]; + if (entry.code_point_ == c) { + if (entry.offset_ == 0) { + return 0; + } else { + result[0] = c + entry.offset_; + return 1; + } + } else { + return CalculateValue(c, n, result); + } + } + + private: + int CalculateValue(uchar c, uchar n, uchar* result) { + bool allow_caching = true; + int length = T::Convert(c, n, result, &allow_caching); + if (allow_caching) { + if (length == 1) { + entries_[c & kMask] = CacheEntry(c, result[0] - c); + return 1; + } else { + entries_[c & kMask] = CacheEntry(c, 0); + return 0; + } + } else { + return length; + } + } + + struct CacheEntry { + inline CacheEntry() : code_point_(kNoChar), offset_(0) {} + inline CacheEntry(uchar code_point, signed offset) + : code_point_(code_point), offset_(offset) {} + uchar code_point_; + signed offset_; + static const int kNoChar = (1 << 21) - 1; + }; + static const int kSize = size; + static const int kMask = kSize - 1; + CacheEntry entries_[kSize]; +}; + +// Origin: +// https://github.com/v8/v8/blob/b4bfbce6f91fc2cc72178af42bb3172c5f5eaebb/src/strings/unicode.h#L241-L252 +struct Ecma262Canonicalize { + static const int kMaxWidth = 1; + static int Convert(uchar c, uchar n, uchar* result, bool* allow_caching_ptr); +}; +struct Ecma262UnCanonicalize { + static const int kMaxWidth = 4; + static int Convert(uchar c, uchar n, uchar* result, bool* allow_caching_ptr); +}; +struct CanonicalizationRange { + static const int kMaxWidth = 1; + static int Convert(uchar c, uchar n, uchar* result, bool* allow_caching_ptr); +}; + +#endif // !V8_INTL_SUPPORT + +struct Letter { + static bool Is(uchar c); +}; + +} // namespace unibrow + +namespace internal { + +#define PRINTF_FORMAT(x, y) MOZ_FORMAT_PRINTF(x, y) +void PRINTF_FORMAT(1, 2) PrintF(const char* format, ...); +void PRINTF_FORMAT(2, 3) PrintF(FILE* out, const char* format, ...); + +// Superclass for classes only using static method functions. +// The subclass of AllStatic cannot be instantiated at all. +class AllStatic { +#ifdef DEBUG + public: + AllStatic() = delete; +#endif +}; + +// Superclass for classes managed with new and delete. +// In irregexp, this is only AlternativeGeneration (in regexp-compiler.cc) +// Compare: +// https://github.com/v8/v8/blob/7b3332844212d78ee87a9426f3a6f7f781a8fbfa/src/utils/allocation.cc#L88-L96 +class Malloced { + public: + static void* operator new(size_t size) { + js::AutoEnterOOMUnsafeRegion oomUnsafe; + void* result = js_malloc(size); + if (!result) { + oomUnsafe.crash("Irregexp Malloced shim"); + } + return result; + } + static void operator delete(void* p) { js_free(p); } +}; + +constexpr int32_t KB = 1024; +constexpr int32_t MB = 1024 * 1024; + +#define kMaxInt JSVAL_INT_MAX +#define kMinInt JSVAL_INT_MIN +constexpr int kSystemPointerSize = sizeof(void*); + +// The largest integer n such that n and n + 1 are both exactly +// representable as a Number value. ES6 section 20.1.2.6 +constexpr double kMaxSafeInteger = 9007199254740991.0; // 2^53-1 + +constexpr int kBitsPerByte = 8; +constexpr int kBitsPerByteLog2 = 3; +constexpr int kUInt16Size = sizeof(uint16_t); +constexpr int kUInt32Size = sizeof(uint32_t); +constexpr int kInt64Size = sizeof(int64_t); + +constexpr int kMaxUInt16 = (1 << 16) - 1; + +inline constexpr bool IsDecimalDigit(base::uc32 c) { + return c >= '0' && c <= '9'; +} + +inline constexpr int AsciiAlphaToLower(base::uc32 c) { return c | 0x20; } + +inline bool is_uint24(int64_t val) { return (val >> 24) == 0; } +inline bool is_int24(int64_t val) { + int64_t limit = int64_t(1) << 23; + return (-limit <= val) && (val < limit); +} + +inline bool IsIdentifierStart(base::uc32 c) { + return js::unicode::IsIdentifierStart(char32_t(c)); +} +inline bool IsIdentifierPart(base::uc32 c) { + return js::unicode::IsIdentifierPart(char32_t(c)); +} + +// Wrappers to disambiguate char16_t and uc16. +struct AsUC16 { + explicit AsUC16(char16_t v) : value(v) {} + char16_t value; +}; + +struct AsUC32 { + explicit AsUC32(int32_t v) : value(v) {} + int32_t value; +}; + +std::ostream& operator<<(std::ostream& os, const AsUC16& c); +std::ostream& operator<<(std::ostream& os, const AsUC32& c); + +// This class is used for the output of trace-regexp-parser. V8 has +// an elaborate implementation to ensure that the output gets to the +// right place, even on Android. We just need something that will +// print output (ideally to stderr, to match the rest of our tracing +// code). This is an empty wrapper that will convert itself to +// std::cerr when used. +class StdoutStream { + public: + operator std::ostream&() const; + template <typename T> + std::ostream& operator<<(T t); +}; + +// Reuse existing Maybe implementation +using mozilla::Maybe; + +template <typename T> +Maybe<T> Just(const T& value) { + return mozilla::Some(value); +} + +template <typename T> +mozilla::Nothing Nothing() { + return mozilla::Nothing(); +} + +template <typename T> +using PseudoHandle = mozilla::UniquePtr<T, JS::FreePolicy>; + +// Compare 8bit/16bit chars to 8bit/16bit chars. +// Used indirectly by regexp-interpreter.cc +// Taken from: https://github.com/v8/v8/blob/master/src/utils/utils.h +template <typename lchar, typename rchar> +inline int CompareCharsUnsigned(const lchar* lhs, const rchar* rhs, + size_t chars) { + const lchar* limit = lhs + chars; + if (sizeof(*lhs) == sizeof(char) && sizeof(*rhs) == sizeof(char)) { + // memcmp compares byte-by-byte, yielding wrong results for two-byte + // strings on little-endian systems. + return memcmp(lhs, rhs, chars); + } + while (lhs < limit) { + int r = static_cast<int>(*lhs) - static_cast<int>(*rhs); + if (r != 0) return r; + ++lhs; + ++rhs; + } + return 0; +} +template <typename lchar, typename rchar> +inline int CompareChars(const lchar* lhs, const rchar* rhs, size_t chars) { + DCHECK_LE(sizeof(lchar), 2); + DCHECK_LE(sizeof(rchar), 2); + if (sizeof(lchar) == 1) { + if (sizeof(rchar) == 1) { + return CompareCharsUnsigned(reinterpret_cast<const uint8_t*>(lhs), + reinterpret_cast<const uint8_t*>(rhs), chars); + } else { + return CompareCharsUnsigned(reinterpret_cast<const uint8_t*>(lhs), + reinterpret_cast<const char16_t*>(rhs), + chars); + } + } else { + if (sizeof(rchar) == 1) { + return CompareCharsUnsigned(reinterpret_cast<const char16_t*>(lhs), + reinterpret_cast<const uint8_t*>(rhs), chars); + } else { + return CompareCharsUnsigned(reinterpret_cast<const char16_t*>(lhs), + reinterpret_cast<const char16_t*>(rhs), + chars); + } + } +} + +// Compare 8bit/16bit chars to 8bit/16bit chars. +template <typename lchar, typename rchar> +inline bool CompareCharsEqualUnsigned(const lchar* lhs, const rchar* rhs, + size_t chars) { + STATIC_ASSERT(std::is_unsigned<lchar>::value); + STATIC_ASSERT(std::is_unsigned<rchar>::value); + if (sizeof(*lhs) == sizeof(*rhs)) { + // memcmp compares byte-by-byte, but for equality it doesn't matter whether + // two-byte char comparison is little- or big-endian. + return memcmp(lhs, rhs, chars * sizeof(*lhs)) == 0; + } + for (const lchar* limit = lhs + chars; lhs < limit; ++lhs, ++rhs) { + if (*lhs != *rhs) return false; + } + return true; +} + +template <typename lchar, typename rchar> +inline bool CompareCharsEqual(const lchar* lhs, const rchar* rhs, + size_t chars) { + using ulchar = typename std::make_unsigned<lchar>::type; + using urchar = typename std::make_unsigned<rchar>::type; + return CompareCharsEqualUnsigned(reinterpret_cast<const ulchar*>(lhs), + reinterpret_cast<const urchar*>(rhs), chars); +} + +// V8::Object ~= JS::Value +class Object { + public: + // The default object constructor in V8 stores a nullptr, + // which has its low bit clear and is interpreted as Smi(0). + constexpr Object() : asBits_(JS::Int32Value(0).asRawBits()) {} + + Object(const JS::Value& value) : asBits_(value.asRawBits()) {} + + // This constructor is only used in an unused implementation of + // IsCharacterInRangeArray in regexp-macro-assembler.cc. + Object(uintptr_t raw) : asBits_(raw) { MOZ_CRASH("unused"); } + + // Used in regexp-interpreter.cc to check the return value of + // isolate->stack_guard()->HandleInterrupts(). We want to handle + // interrupts in the caller, so we always return false from + // HandleInterrupts and true here. + inline bool IsException(Isolate*) const { + MOZ_ASSERT(!value().toBoolean()); + return true; + } + + JS::Value value() const { return JS::Value::fromRawBits(asBits_); } + + inline static Object cast(Object object) { return object; } + + protected: + void setValue(const JS::Value& val) { asBits_ = val.asRawBits(); } + uint64_t asBits_; +} JS_HAZ_GC_POINTER; + +class Smi : public Object { + public: + static Smi FromInt(int32_t value) { + Smi smi; + smi.setValue(JS::Int32Value(value)); + return smi; + } + static inline int32_t ToInt(const Object object) { + return object.value().toInt32(); + } +}; + +// V8::HeapObject ~= GC thing +class HeapObject : public Object { + public: + inline static HeapObject cast(Object object) { + HeapObject h; + h.setValue(object.value()); + return h; + } +}; + +// A fixed-size array with Objects (aka Values) as element types. +// Implemented using the dense elements of an ArrayObject. +// Used for named captures. +class FixedArray : public HeapObject { + public: + inline void set(uint32_t index, Object value) { + inner()->setDenseElement(index, value.value()); + } + inline static FixedArray cast(Object object) { + FixedArray f; + f.setValue(object.value()); + return f; + } + js::NativeObject* inner() { + return &value().toObject().as<js::NativeObject>(); + } +}; + +/* + * Conceptually, ByteArrayData is a variable-size structure. To + * implement this in a C++-approved way, we allocate a struct + * containing the 32-bit length field, followed by additional memory + * for the data. To access the data, we get a pointer to the next byte + * after the length field and cast it to the correct type. + */ +inline uint8_t* ByteArrayData::data() { + static_assert(alignof(uint8_t) <= alignof(ByteArrayData), + "The trailing data must be aligned to start immediately " + "after the header with no padding."); + ByteArrayData* immediatelyAfter = this + 1; + return reinterpret_cast<uint8_t*>(immediatelyAfter); +} + +template <typename T> +T* ByteArrayData::typedData() { + static_assert(alignof(T) <= alignof(ByteArrayData)); + MOZ_ASSERT(uintptr_t(data()) % alignof(T) == 0); + return reinterpret_cast<T*>(data()); +} + +template <typename T> +T ByteArrayData::getTyped(uint32_t index) { + MOZ_ASSERT(index < length / sizeof(T)); + return typedData<T>()[index]; +} + +template <typename T> +void ByteArrayData::setTyped(uint32_t index, T value) { + MOZ_ASSERT(index < length / sizeof(T)); + typedData<T>()[index] = value; +} + +// A fixed-size array of bytes. +class ByteArray : public HeapObject { + protected: + ByteArrayData* inner() const { + return static_cast<ByteArrayData*>(value().toPrivate()); + } + + public: + PseudoHandle<ByteArrayData> takeOwnership(Isolate* isolate); + PseudoHandle<ByteArrayData> maybeTakeOwnership(Isolate* isolate); + + uint8_t get(uint32_t index) { return inner()->get(index); } + void set(uint32_t index, uint8_t val) { inner()->set(index, val); } + + uint32_t length() const { return inner()->length; } + uint8_t* GetDataStartAddress() { return inner()->data(); } + + static ByteArray cast(Object object) { + ByteArray b; + b.setValue(object.value()); + return b; + } + + bool IsByteArray() const { return true; } + + friend class SMRegExpMacroAssembler; +}; + +// This is a convenience class used in V8 for treating a ByteArray as an array +// of fixed-size integers. This version supports integral types up to 32 bits. +template <typename T> +class FixedIntegerArray : public ByteArray { + static_assert(alignof(T) <= alignof(ByteArrayData)); + static_assert(std::is_integral<T>::value); + + public: + static Handle<FixedIntegerArray<T>> New(Isolate* isolate, uint32_t length); + + T get(uint32_t index) { return inner()->template getTyped<T>(index); }; + void set(uint32_t index, T value) { + inner()->template setTyped<T>(index, value); + } + + static FixedIntegerArray<T> cast(Object object) { + FixedIntegerArray<T> f; + f.setValue(object.value()); + return f; + } +}; + +using FixedUInt16Array = FixedIntegerArray<uint16_t>; + +// Like Handles in SM, V8 handles are references to marked pointers. +// Unlike SM, where Rooted pointers are created individually on the +// stack, the target of a V8 handle lives in an arena on the isolate +// (~= JSContext). Whenever a Handle is created, a new "root" is +// created at the end of the arena. +// +// HandleScopes are used to manage the lifetimes of these handles. A +// HandleScope lives on the stack and stores the size of the arena at +// the time of its creation. When the function returns and the +// HandleScope is destroyed, the arena is truncated to its previous +// size, clearing all roots that were created since the creation of +// the HandleScope. +// +// In some cases, objects that are GC-allocated in V8 are not in SM. +// In particular, irregexp allocates ByteArrays during code generation +// to store lookup tables. This does not play nicely with the SM +// macroassembler's requirement that no GC allocations take place +// while it is on the stack. To work around this, this shim layer also +// provides the ability to create pseudo-handles, which are not +// managed by the GC but provide the same API to irregexp. The "root" +// of a pseudohandle is a unique pointer living in a second arena. If +// the allocated object should outlive the HandleScope, it must be +// manually moved out of the arena using maybeTakeOwnership. +// (If maybeTakeOwnership is called multiple times, it will return +// a null pointer on subsequent calls.) + +class MOZ_STACK_CLASS HandleScope { + public: + HandleScope(Isolate* isolate); + ~HandleScope(); + + private: + size_t level_ = 0; + size_t non_gc_level_ = 0; + Isolate* isolate_; + + friend class Isolate; +}; + +// Origin: +// https://github.com/v8/v8/blob/5792f3587116503fc047d2f68c951c72dced08a5/src/handles/handles.h#L88-L171 +template <typename T> +class MOZ_NONHEAP_CLASS Handle { + public: + Handle() : location_(nullptr) {} + Handle(T object, Isolate* isolate); + Handle(const JS::Value& value, Isolate* isolate); + + // Constructor for handling automatic up casting. + template <typename S, + typename = std::enable_if_t<std::is_convertible_v<S*, T*>>> + inline Handle(Handle<S> handle) : location_(handle.location_) {} + + inline bool is_null() const { return location_ == nullptr; } + + inline T operator*() const { return T::cast(Object(*location_)); }; + + // {ObjectRef} is returned by {Handle::operator->}. It should never be stored + // anywhere or used in any other code; no one should ever have to spell out + // {ObjectRef} in code. Its only purpose is to be dereferenced immediately by + // "operator-> chaining". Returning the address of the field is valid because + // this object's lifetime only ends at the end of the full statement. + // Origin: + // https://github.com/v8/v8/blob/03aaa4b3bf4cb01eee1f223b252e6869b04ab08c/src/handles/handles.h#L91-L105 + class MOZ_TEMPORARY_CLASS ObjectRef { + public: + T* operator->() { return &object_; } + + private: + friend class Handle; + explicit ObjectRef(T object) : object_(object) {} + + T object_; + }; + inline ObjectRef operator->() const { return ObjectRef{**this}; } + + static Handle<T> fromHandleValue(JS::HandleValue handle) { + return Handle(handle.address()); + } + + private: + Handle(const JS::Value* location) : location_(location) {} + + template <typename> + friend class Handle; + template <typename> + friend class MaybeHandle; + + const JS::Value* location_; +}; + +// A Handle can be converted into a MaybeHandle. Converting a MaybeHandle +// into a Handle requires checking that it does not point to nullptr. This +// ensures nullptr checks before use. +// +// Also note that Handles do not provide default equality comparison or hashing +// operators on purpose. Such operators would be misleading, because intended +// semantics is ambiguous between Handle location and object identity. +// Origin: +// https://github.com/v8/v8/blob/5792f3587116503fc047d2f68c951c72dced08a5/src/handles/maybe-handles.h#L15-L78 +template <typename T> +class MOZ_NONHEAP_CLASS MaybeHandle final { + public: + MaybeHandle() : location_(nullptr) {} + + // Constructor for handling automatic up casting from Handle. + // Ex. Handle<JSArray> can be passed when MaybeHandle<Object> is expected. + template <typename S, + typename = std::enable_if_t<std::is_convertible_v<S*, T*>>> + MaybeHandle(Handle<S> handle) : location_(handle.location_) {} + + inline Handle<T> ToHandleChecked() const { + MOZ_RELEASE_ASSERT(location_); + return Handle<T>(location_); + } + + // Convert to a Handle with a type that can be upcasted to. + template <typename S> + inline bool ToHandle(Handle<S>* out) const { + if (location_) { + *out = Handle<T>(location_); + return true; + } else { + *out = Handle<T>(); + return false; + } + } + + private: + JS::Value* location_; +}; + +// From v8/src/handles/handles-inl.h + +template <typename T> +inline Handle<T> handle(T object, Isolate* isolate) { + return Handle<T>(object, isolate); +} + +// RAII Guard classes + +using DisallowGarbageCollection = JS::AutoAssertNoGC; + +// V8 uses this inside DisallowGarbageCollection regions to turn +// allocation back on before throwing a stack overflow exception or +// handling interrupts. AutoSuppressGC is sufficient for the former +// case, but not for the latter: handling interrupts can execute +// arbitrary script code, and V8 jumps through some scary hoops to +// "manually relocate unhandlified references" afterwards. To keep +// things sane, we don't try to handle interrupts while regex code is +// still on the stack. Instead, we return EXCEPTION and handle +// interrupts in the caller. (See RegExpShared::execute.) + +class AllowGarbageCollection { + public: + AllowGarbageCollection() {} +}; + +// Origin: +// https://github.com/v8/v8/blob/84f3877c15bc7f8956d21614da4311337525a3c8/src/objects/string.h#L83-L474 +class String : public HeapObject { + private: + JSString* str() const { return value().toString(); } + + public: + String() = default; + String(JSString* str) { setValue(JS::StringValue(str)); } + + operator JSString*() const { return str(); } + + // Max char codes. + static const int32_t kMaxOneByteCharCode = unibrow::Latin1::kMaxChar; + static const uint32_t kMaxOneByteCharCodeU = unibrow::Latin1::kMaxChar; + static const int kMaxUtf16CodeUnit = 0xffff; + static const uint32_t kMaxUtf16CodeUnitU = kMaxUtf16CodeUnit; + static const base::uc32 kMaxCodePoint = 0x10ffff; + + MOZ_ALWAYS_INLINE int length() const { return str()->length(); } + bool IsFlat() { return str()->isLinear(); }; + + // Origin: + // https://github.com/v8/v8/blob/84f3877c15bc7f8956d21614da4311337525a3c8/src/objects/string.h#L95-L152 + class FlatContent { + public: + FlatContent(JSLinearString* string, const DisallowGarbageCollection& no_gc) + : string_(string), no_gc_(no_gc) {} + inline bool IsOneByte() const { return string_->hasLatin1Chars(); } + inline bool IsTwoByte() const { return !string_->hasLatin1Chars(); } + + base::Vector<const uint8_t> ToOneByteVector() const { + MOZ_ASSERT(IsOneByte()); + return base::Vector<const uint8_t>(string_->latin1Chars(no_gc_), + string_->length()); + } + base::Vector<const base::uc16> ToUC16Vector() const { + MOZ_ASSERT(IsTwoByte()); + return base::Vector<const base::uc16>(string_->twoByteChars(no_gc_), + string_->length()); + } + void UnsafeDisableChecksumVerification() { + // Intentional no-op. See the comment for AllowGarbageCollection above. + } + + private: + const JSLinearString* string_; + const JS::AutoAssertNoGC& no_gc_; + }; + FlatContent GetFlatContent(const DisallowGarbageCollection& no_gc) { + MOZ_ASSERT(IsFlat()); + return FlatContent(&str()->asLinear(), no_gc); + } + + static Handle<String> Flatten(Isolate* isolate, Handle<String> string); + + inline static String cast(Object object) { + String s; + MOZ_ASSERT(object.value().isString()); + s.setValue(object.value()); + return s; + } + + inline static bool IsOneByteRepresentationUnderneath(String string) { + return string.str()->hasLatin1Chars(); + } + inline bool IsOneByteRepresentation() const { + return str()->hasLatin1Chars(); + } + + std::unique_ptr<char[]> ToCString(); + + template <typename Char> + base::Vector<const Char> GetCharVector( + const DisallowGarbageCollection& no_gc); +}; + +template <> +inline base::Vector<const uint8_t> String::GetCharVector( + const DisallowGarbageCollection& no_gc) { + String::FlatContent flat = GetFlatContent(no_gc); + MOZ_ASSERT(flat.IsOneByte()); + return flat.ToOneByteVector(); +} + +template <> +inline base::Vector<const base::uc16> String::GetCharVector( + const DisallowGarbageCollection& no_gc) { + String::FlatContent flat = GetFlatContent(no_gc); + MOZ_ASSERT(flat.IsTwoByte()); + return flat.ToUC16Vector(); +} + +class JSRegExp : public HeapObject { + public: + JSRegExp() : HeapObject() {} + JSRegExp(js::RegExpShared* re) { setValue(JS::PrivateGCThingValue(re)); } + + // ****************************************************** + // Methods that are called from inside the implementation + // ****************************************************** + void TierUpTick() { inner()->tierUpTick(); } + + Object bytecode(bool is_latin1) const { + return Object(JS::PrivateValue(inner()->getByteCode(is_latin1))); + } + + // TODO: should we expose this? + uint32_t backtrack_limit() const { return 0; } + + static JSRegExp cast(Object object) { + JSRegExp regexp; + js::gc::Cell* regexpShared = object.value().toGCThing(); + MOZ_ASSERT(regexpShared->is<js::RegExpShared>()); + regexp.setValue(JS::PrivateGCThingValue(regexpShared)); + return regexp; + } + + // Each capture (including the match itself) needs two registers. + static constexpr int RegistersForCaptureCount(int count) { + return (count + 1) * 2; + } + + inline uint32_t max_register_count() const { + return inner()->getMaxRegisters(); + } + + // ****************************** + // Static constants + // ****************************** + + static constexpr int kMaxCaptures = (1 << 15) - 1; + + static constexpr int kNoBacktrackLimit = 0; + + private: + js::RegExpShared* inner() const { + return value().toGCThing()->as<js::RegExpShared>(); + } +}; + +using RegExpFlags = JS::RegExpFlags; + +inline bool IsUnicode(RegExpFlags flags) { return flags.unicode(); } +inline bool IsGlobal(RegExpFlags flags) { return flags.global(); } +inline bool IsIgnoreCase(RegExpFlags flags) { return flags.ignoreCase(); } +inline bool IsMultiline(RegExpFlags flags) { return flags.multiline(); } +inline bool IsDotAll(RegExpFlags flags) { return flags.dotAll(); } +inline bool IsSticky(RegExpFlags flags) { return flags.sticky(); } +inline bool IsUnicodeSets(RegExpFlags flags) { return flags.unicodeSets(); } +inline bool IsEitherUnicode(RegExpFlags flags) { + return flags.unicode() || flags.unicodeSets(); +} + +class Histogram { + public: + inline void AddSample(int sample) {} +}; + +class Counters { + public: + Histogram* regexp_backtracks() { return ®exp_backtracks_; } + + private: + Histogram regexp_backtracks_; +}; + +enum class AllocationType : uint8_t { + kYoung, // Allocate in the nursery + kOld, // Allocate in the tenured heap +}; + +using StackGuard = Isolate; +using Factory = Isolate; + +class Isolate { + public: + Isolate(JSContext* cx) : cx_(cx) {} + ~Isolate(); + bool init(); + + size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const; + + //********** Isolate code **********// + RegExpStack* regexp_stack() const { return regexpStack_; } + + // This is called from inside no-GC code. Instead of suppressing GC + // to allocate the error, we return false from Execute and call + // ReportOverRecursed in the caller. + void StackOverflow() {} + +#ifndef V8_INTL_SUPPORT + unibrow::Mapping<unibrow::Ecma262UnCanonicalize>* jsregexp_uncanonicalize() { + return &jsregexp_uncanonicalize_; + } + unibrow::Mapping<unibrow::Ecma262Canonicalize>* + regexp_macro_assembler_canonicalize() { + return ®exp_macro_assembler_canonicalize_; + } + unibrow::Mapping<unibrow::CanonicalizationRange>* jsregexp_canonrange() { + return &jsregexp_canonrange_; + } + + private: + unibrow::Mapping<unibrow::Ecma262UnCanonicalize> jsregexp_uncanonicalize_; + unibrow::Mapping<unibrow::Ecma262Canonicalize> + regexp_macro_assembler_canonicalize_; + unibrow::Mapping<unibrow::CanonicalizationRange> jsregexp_canonrange_; +#endif // !V8_INTL_SUPPORT + + public: + // An empty stub for telemetry we don't support + void IncreaseTotalRegexpCodeGenerated(Handle<HeapObject> code) {} + + Counters* counters() { return &counters_; } + + //********** Factory code **********// + inline Factory* factory() { return this; } + + Handle<ByteArray> NewByteArray( + int length, AllocationType allocation = AllocationType::kYoung); + + // Allocates a fixed array initialized with undefined values. + Handle<FixedArray> NewFixedArray(int length); + + template <typename T> + Handle<FixedIntegerArray<T>> NewFixedIntegerArray(uint32_t length); + + template <typename Char> + Handle<String> InternalizeString(const base::Vector<const Char>& str); + + //********** Stack guard code **********// + inline StackGuard* stack_guard() { return this; } + + uintptr_t real_climit() { return cx_->stackLimit(JS::StackForSystemCode); } + + // This is called from inside no-GC code. V8 runs the interrupt + // inside the no-GC code and then "manually relocates unhandlified + // references" afterwards. We just return false and let the caller + // handle interrupts. + Object HandleInterrupts() { return Object(JS::BooleanValue(false)); } + + JSContext* cx() const { return cx_; } + + void trace(JSTracer* trc); + + //********** Handle code **********// + + JS::Value* getHandleLocation(const JS::Value& value); + + private: + mozilla::SegmentedVector<JS::Value, 256> handleArena_; + mozilla::SegmentedVector<PseudoHandle<void>, 256> uniquePtrArena_; + + void* allocatePseudoHandle(size_t bytes); + + public: + template <typename T> + PseudoHandle<T> takeOwnership(void* ptr); + template <typename T> + PseudoHandle<T> maybeTakeOwnership(void* ptr); + + uint32_t liveHandles() const { return handleArena_.Length(); } + uint32_t livePseudoHandles() const { return uniquePtrArena_.Length(); } + + private: + void openHandleScope(HandleScope& scope) { + scope.level_ = handleArena_.Length(); + scope.non_gc_level_ = uniquePtrArena_.Length(); + } + void closeHandleScope(size_t prevLevel, size_t prevUniqueLevel) { + size_t currLevel = handleArena_.Length(); + handleArena_.PopLastN(currLevel - prevLevel); + + size_t currUniqueLevel = uniquePtrArena_.Length(); + uniquePtrArena_.PopLastN(currUniqueLevel - prevUniqueLevel); + } + friend class HandleScope; + + JSContext* cx_; + RegExpStack* regexpStack_{}; + Counters counters_{}; +#ifdef DEBUG + public: + uint32_t shouldSimulateInterrupt_ = 0; +#endif +}; + +// Origin: +// https://github.com/v8/v8/blob/50dcf2af54ce27801a71c47c1be1d2c5e36b0dd6/src/execution/isolate.h#L1909-L1931 +class StackLimitCheck { + public: + StackLimitCheck(Isolate* isolate) : cx_(isolate->cx()) {} + + // Use this to check for stack-overflows in C++ code. + bool HasOverflowed() { + js::AutoCheckRecursionLimit recursion(cx_); + bool overflowed = !recursion.checkDontReport(cx_); + if (overflowed && js::SupportDifferentialTesting()) { + // We don't report overrecursion here, but we throw an exception later + // and this still affects differential testing. Mimic ReportOverRecursed + // (the fuzzers check for this particular string). + fprintf(stderr, "ReportOverRecursed called\n"); + } + return overflowed; + } + + // Use this to check for interrupt request in C++ code. + bool InterruptRequested() { + return cx_->hasPendingInterrupt(js::InterruptReason::CallbackUrgent); + } + + // Use this to check for stack-overflow when entering runtime from JS code. + bool JsHasOverflowed() { + js::AutoCheckRecursionLimit recursion(cx_); + return !recursion.checkDontReport(cx_); + } + + private: + JSContext* cx_; +}; + +class ExternalReference { + public: + static const void* TopOfRegexpStack(Isolate* isolate); + static size_t SizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf, + RegExpStack* regexpStack); +}; + +class Code : public HeapObject { + public: + uint8_t* raw_instruction_start() { return inner()->raw(); } + + static Code cast(Object object) { + Code c; + js::gc::Cell* jitCode = object.value().toGCThing(); + MOZ_ASSERT(jitCode->is<js::jit::JitCode>()); + c.setValue(JS::PrivateGCThingValue(jitCode)); + return c; + } + js::jit::JitCode* inner() { + return value().toGCThing()->as<js::jit::JitCode>(); + } +}; + +// Only used in function signature of functions we don't implement +// (NativeRegExpMacroAssembler::CheckStackGuardState) +class InstructionStream {}; + +// Origin: https://github.com/v8/v8/blob/master/src/codegen/label.h +class Label { + public: + Label() : inner_(js::jit::Label()) {} + + js::jit::Label* inner() { return &inner_; } + + void Unuse() { inner_.reset(); } + + bool is_linked() { return inner_.used(); } + bool is_bound() { return inner_.bound(); } + bool is_unused() { return !inner_.used() && !inner_.bound(); } + + int pos() { return inner_.offset(); } + void link_to(int pos) { inner_.use(pos); } + void bind_to(int pos) { inner_.bind(pos); } + + private: + js::jit::Label inner_; + js::jit::CodeOffset patchOffset_; + + friend class SMRegExpMacroAssembler; +}; + +#define v8_flags js::jit::JitOptions + +#define V8_USE_COMPUTED_GOTO 1 +#define COMPILING_IRREGEXP_FOR_EXTERNAL_EMBEDDER + +} // namespace internal +} // namespace v8 + +namespace V8 { + +inline void FatalProcessOutOfMemory(v8::internal::Isolate* isolate, + const char* msg) { + js::AutoEnterOOMUnsafeRegion oomUnsafe; + oomUnsafe.crash(msg); +} + +} // namespace V8 + +#endif // RegexpShim_h diff --git a/js/src/irregexp/RegExpTypes.h b/js/src/irregexp/RegExpTypes.h new file mode 100644 index 0000000000..e2a619689c --- /dev/null +++ b/js/src/irregexp/RegExpTypes.h @@ -0,0 +1,100 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// This file forward-defines Irregexp classes that need to be visible +// to the rest of Spidermonkey and re-exports them into js::irregexp. + +#ifndef regexp_RegExpTypes_h +#define regexp_RegExpTypes_h + +#include "js/UniquePtr.h" + +namespace js { +class MatchPairs; +} + +namespace v8 { +namespace internal { + +class ByteArrayData { + public: + uint32_t length; + uint8_t* data(); + + uint8_t get(uint32_t index) { + MOZ_ASSERT(index < length); + return data()[index]; + } + void set(uint32_t index, uint8_t val) { + MOZ_ASSERT(index < length); + data()[index] = val; + } + + // Used for FixedIntegerArray. + template <typename T> + T getTyped(uint32_t index); + template <typename T> + void setTyped(uint32_t index, T value); + + private: + template <typename T> + T* typedData(); +}; + +class Isolate; +class RegExpStack; +class RegExpStackScope; + +struct InputOutputData { + const void* inputStart; + const void* inputEnd; + + // Index into inputStart (in chars) at which to begin matching. + size_t startIndex; + + js::MatchPairs* matches; + + template <typename CharT> + InputOutputData(const CharT* inputStart, const CharT* inputEnd, + size_t startIndex, js::MatchPairs* matches) + : inputStart(inputStart), + inputEnd(inputEnd), + startIndex(startIndex), + matches(matches) {} + + // Note: return int32_t instead of size_t to prevent signed => unsigned + // conversions in caller functions. + static constexpr int32_t offsetOfInputStart() { + return int32_t(offsetof(InputOutputData, inputStart)); + } + static constexpr int32_t offsetOfInputEnd() { + return int32_t(offsetof(InputOutputData, inputEnd)); + } + static constexpr int32_t offsetOfStartIndex() { + return int32_t(offsetof(InputOutputData, startIndex)); + } + static constexpr int32_t offsetOfMatches() { + return int32_t(offsetof(InputOutputData, matches)); + } +}; + +} // namespace internal +} // namespace v8 + +namespace js { +namespace irregexp { + +using Isolate = v8::internal::Isolate; +using RegExpStack = v8::internal::RegExpStack; +using RegExpStackScope = v8::internal::RegExpStackScope; +using ByteArrayData = v8::internal::ByteArrayData; +using ByteArray = js::UniquePtr<v8::internal::ByteArrayData, JS::FreePolicy>; +using InputOutputData = v8::internal::InputOutputData; + +} // namespace irregexp +} // namespace js + +#endif // regexp_RegExpTypes_h diff --git a/js/src/irregexp/import-irregexp.py b/js/src/irregexp/import-irregexp.py new file mode 100755 index 0000000000..523276e7b3 --- /dev/null +++ b/js/src/irregexp/import-irregexp.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 + +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this file, +# You can obtain one at http://mozilla.org/MPL/2.0/. + +# This script handles all the mechanical steps of importing irregexp from v8: +# +# 1. Acquire the source: either from github, or optionally from a local copy of v8. +# 2. Copy the contents of v8/src/regexp into js/src/irregexp/imported +# - Exclude files that we have chosen not to import. +# 3. While doing so, update #includes: +# - Change "src/regexp/*" to "irregexp/imported/*". +# - Remove other v8-specific headers completely. +# 4. Add '#include "irregexp/RegExpShim.h" in the necessary places. +# 5. Update the IRREGEXP_VERSION file to include the correct git hash. +# +# Usage: +# cd path/to/js/src/irregexp +# ./import-irregexp.py --path path/to/v8/src/regexp +# +# Alternatively, without the --path argument, import-irregexp.py will +# clone v8 from github into a temporary directory. +# +# After running this script, changes to the shim code may be necessary +# to account for changes in upstream irregexp. + +import os +import re +import subprocess +import sys +from pathlib import Path + + +def copy_and_update_includes(src_path, dst_path): + # List of header files that need to include the shim header + need_shim = [ + "property-sequences.h", + "regexp-ast.h", + "regexp-bytecode-peephole.h", + "regexp-bytecodes.h", + "regexp-dotprinter.h", + "regexp-error.h", + "regexp.h", + "regexp-macro-assembler.h", + "regexp-parser.h", + "regexp-stack.h", + "special-case.h", + ] + + src = open(str(src_path), "r") + dst = open(str(dst_path), "w") + + # 1. Rewrite includes of V8 regexp headers: + # Note that we exclude regexp-flags.h and provide our own definition. + regexp_include = re.compile('#include "src/regexp(?!/regexp-flags.h)') + regexp_include_new = '#include "irregexp/imported' + + # 2. Remove includes of other V8 headers + other_include = re.compile('#include "src/') + + # 3. If needed, add '#include "irregexp/RegExpShim.h"'. + # Note: We get a little fancy to ensure that header files are + # in alphabetic order. `need_to_add_shim` is true if we still + # have to add the shim header in this file. `adding_shim_now` + # is true if we have found a '#include "src/*' and we are just + # waiting to find an empty line so that we can insert the shim + # header in the right place. + need_to_add_shim = src_path.name in need_shim + adding_shim_now = False + + for line in src: + if adding_shim_now: + if line == "\n": + dst.write('#include "irregexp/RegExpShim.h"\n') + need_to_add_shim = False + adding_shim_now = False + + if regexp_include.search(line): + dst.write(re.sub(regexp_include, regexp_include_new, line)) + elif other_include.search(line): + if need_to_add_shim: + adding_shim_now = True + else: + dst.write(line) + + +def import_from(srcdir, dstdir): + excluded = [ + "DIR_METADATA", + "OWNERS", + "regexp.cc", + "regexp-flags.h", + "regexp-utils.cc", + "regexp-utils.h", + "regexp-macro-assembler-arch.h", + ] + + for file in srcdir.iterdir(): + if file.is_dir(): + continue + if str(file.name) in excluded: + continue + copy_and_update_includes(file, dstdir / "imported" / file.name) + + +if __name__ == "__main__": + import argparse + import tempfile + + # This script should be run from js/src/irregexp to work correctly. + current_path = Path(os.getcwd()) + expected_path = "js/src/irregexp" + if not current_path.match(expected_path): + raise RuntimeError("%s must be run from %s" % (sys.argv[0], expected_path)) + + parser = argparse.ArgumentParser(description="Import irregexp from v8") + parser.add_argument("-p", "--path", help="path to v8/src/regexp", required=False) + args = parser.parse_args() + + if args.path: + src_path = Path(args.path) + provided_path = "the command-line" + elif "TASK_ID" in os.environ: + src_path = Path("/builds/worker/v8/") + subprocess.run("git pull origin master", shell=True, cwd=src_path) + + src_path = Path("/builds/worker/v8/src/regexp") + provided_path = "the hardcoded path in the taskcluster image" + elif "V8_GIT" in os.environ: + src_path = Path(os.environ["V8_GIT"]) + provided_path = "the V8_GIT environment variable" + else: + tempdir = tempfile.TemporaryDirectory() + v8_git = "https://github.com/v8/v8.git" + clone = "git clone --depth 1 %s %s" % (v8_git, tempdir.name) + os.system(clone) + src_path = Path(tempdir.name) / "src/regexp" + provided_path = "the temporary git checkout" + + if not (src_path / "regexp.h").exists(): + print("Could not find regexp.h in the path provided from", provided_path) + print("Usage:\n import-irregexp.py [--path <path/to/v8/src/regexp>]") + sys.exit(1) + + if "MACH_VENDOR" not in os.environ: + print( + "Running this script outside ./mach vendor is not recommended - ", + "You will need to update moz.yaml manually", + ) + print("We recommend instead `./mach vendor js/src/irregexp/moz.yaml`") + response = input("Type Y to continue... ") + if response.lower() != "y": + sys.exit(1) + + import_from(src_path, current_path) diff --git a/js/src/irregexp/imported/gen-regexp-special-case.cc b/js/src/irregexp/imported/gen-regexp-special-case.cc new file mode 100644 index 0000000000..8f6557ed30 --- /dev/null +++ b/js/src/irregexp/imported/gen-regexp-special-case.cc @@ -0,0 +1,214 @@ +// Copyright 2020 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 <fstream> +#include <iomanip> +#include <iostream> +#include <sstream> + +#include "irregexp/imported/special-case.h" +#include "unicode/usetiter.h" + +namespace v8 { +namespace internal { + +static const base::uc32 kSurrogateStart = 0xd800; +static const base::uc32 kSurrogateEnd = 0xdfff; +static const base::uc32 kNonBmpStart = 0x10000; + +// The following code generates "src/regexp/special-case.cc". +void PrintSet(std::ofstream& out, const char* name, + const icu::UnicodeSet& set) { + out << "icu::UnicodeSet Build" << name << "() {\n" + << " icu::UnicodeSet set;\n"; + for (int32_t i = 0; i < set.getRangeCount(); i++) { + if (set.getRangeStart(i) == set.getRangeEnd(i)) { + out << " set.add(0x" << set.getRangeStart(i) << ");\n"; + } else { + out << " set.add(0x" << set.getRangeStart(i) << ", 0x" + << set.getRangeEnd(i) << ");\n"; + } + } + out << " set.freeze();\n" + << " return set;\n" + << "}\n\n"; + + out << "struct " << name << "Data {\n" + << " " << name << "Data() : set(Build" << name << "()) {}\n" + << " const icu::UnicodeSet set;\n" + << "};\n\n"; + + out << "//static\n" + << "const icu::UnicodeSet& RegExpCaseFolding::" << name << "() {\n" + << " static base::LazyInstance<" << name << "Data>::type set =\n" + << " LAZY_INSTANCE_INITIALIZER;\n" + << " return set.Pointer()->set;\n" + << "}\n\n"; +} + +void PrintSpecial(std::ofstream& out) { + icu::UnicodeSet current; + icu::UnicodeSet special_add; + icu::UnicodeSet ignore; + UErrorCode status = U_ZERO_ERROR; + icu::UnicodeSet upper("[\\p{Lu}]", status); + CHECK(U_SUCCESS(status)); + + // Iterate through all chars in BMP except surrogates. + for (UChar32 i = 0; i < static_cast<UChar32>(kNonBmpStart); i++) { + if (i >= static_cast<UChar32>(kSurrogateStart) && + i <= static_cast<UChar32>(kSurrogateEnd)) { + continue; // Ignore surrogate range + } + current.set(i, i); + current.closeOver(USET_CASE_INSENSITIVE); + + // Check to see if all characters in the case-folding equivalence + // class as defined by UnicodeSet::closeOver all map to the same + // canonical value. + UChar32 canonical = RegExpCaseFolding::Canonicalize(i); + bool class_has_matching_canonical_char = false; + bool class_has_non_matching_canonical_char = false; + for (int32_t j = 0; j < current.getRangeCount(); j++) { + for (UChar32 c = current.getRangeStart(j); c <= current.getRangeEnd(j); + c++) { + if (c == i) { + continue; + } + UChar32 other_canonical = RegExpCaseFolding::Canonicalize(c); + if (canonical == other_canonical) { + class_has_matching_canonical_char = true; + } else { + class_has_non_matching_canonical_char = true; + } + } + } + // If any other character in i's equivalence class has a + // different canonical value, then i needs special handling. If + // no other character shares a canonical value with i, we can + // ignore i when adding alternatives for case-independent + // comparison. If at least one other character shares a + // canonical value, then i needs special handling. + if (class_has_non_matching_canonical_char) { + if (class_has_matching_canonical_char) { + special_add.add(i); + } else { + ignore.add(i); + } + } + } + + // Verify that no Unicode equivalence class contains two non-trivial + // JS equivalence classes. Every character in SpecialAddSet has the + // same canonical value as every other non-IgnoreSet character in + // its Unicode equivalence class. Therefore, if we call closeOver on + // a set containing no IgnoreSet characters, the only characters + // that must be removed from the result are in IgnoreSet. This fact + // is used in CharacterRange::AddCaseEquivalents. + for (int32_t i = 0; i < special_add.getRangeCount(); i++) { + for (UChar32 c = special_add.getRangeStart(i); + c <= special_add.getRangeEnd(i); c++) { + UChar32 canonical = RegExpCaseFolding::Canonicalize(c); + current.set(c, c); + current.closeOver(USET_CASE_INSENSITIVE); + current.removeAll(ignore); + for (int32_t j = 0; j < current.getRangeCount(); j++) { + for (UChar32 c2 = current.getRangeStart(j); + c2 <= current.getRangeEnd(j); c2++) { + CHECK_EQ(canonical, RegExpCaseFolding::Canonicalize(c2)); + } + } + } + } + + 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" + << "// can be found in the LICENSE file.\n\n" + << "// Automatically generated by regexp/gen-regexp-special-case.cc\n\n" + << "// The following functions are used to build UnicodeSets\n" + << "// for special cases where the case-folding algorithm used by\n" + << "// UnicodeSet::closeOver(USET_CASE_INSENSITIVE) does not match\n" + << "// the algorithm defined in ECMAScript 2020 21.2.2.8.2 (Runtime\n" + << "// Semantics: Canonicalize) step 3.\n\n" + << "#ifdef V8_INTL_SUPPORT\n" + << "#include \"src/base/lazy-instance.h\"\n\n" + << "#include \"src/regexp/special-case.h\"\n\n" + << "#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"; +} + +} // namespace internal +} // namespace v8 + +int main(int argc, const char** argv) { + if (argc != 2) { + std::cerr << "Usage: " << argv[0] << " <output filename>\n"; + std::exit(1); + } + v8::internal::WriteHeader(argv[1]); + + return 0; +} diff --git a/js/src/irregexp/imported/property-sequences.cc b/js/src/irregexp/imported/property-sequences.cc new file mode 100644 index 0000000000..b37ec63115 --- /dev/null +++ b/js/src/irregexp/imported/property-sequences.cc @@ -0,0 +1,1246 @@ +// Copyright 2018 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. + +#ifdef V8_INTL_SUPPORT + +#include "irregexp/imported/property-sequences.h" + +namespace v8 { +namespace internal { + +/* +Generated from following Node.js source: + +package.json + +``` +{ + "private": true, + "dependencies": { + "unicode-12.0.0": "^0.7.9" + } +} +``` + +generate-unicode-sequence-property-data.js + +``` +const toHex = (symbol) => { + return '0x' + symbol.codePointAt(0).toString(16) + .toUpperCase().padStart(6, '0'); +}; + +const generateData = (property) => { + const sequences = + require(`unicode-12.0.0/Sequence_Property/${ property }/index.js`); + const id = property.replace(/_/g, '') + 's'; + const buffer = []; + for (const sequence of sequences) { + const symbols = [...sequence]; + const codePoints = symbols.map(symbol => toHex(symbol)); + buffer.push(' ' + codePoints.join(', ') + ', 0,'); + } + const output = + `const base::uc32 UnicodePropertySequences::k${ id }[] = {\n` + + `${ buffer.join('\n') }\n 0 // null-terminating the list\n};\n`; + return output; +}; + +const properties = [ + 'Emoji_Flag_Sequence', + 'Emoji_Tag_Sequence', + 'Emoji_ZWJ_Sequence', +]; + +for (const property of properties) { + console.log(generateData(property)); +} +``` +*/ + +// clang-format off +const base::uc32 UnicodePropertySequences::kEmojiFlagSequences[] = { + 0x01F1E6, 0x01F1E8, 0, + 0x01F1FF, 0x01F1FC, 0, + 0x01F1E6, 0x01F1EA, 0, + 0x01F1E6, 0x01F1EB, 0, + 0x01F1E6, 0x01F1EC, 0, + 0x01F1E6, 0x01F1EE, 0, + 0x01F1E6, 0x01F1F1, 0, + 0x01F1E6, 0x01F1F2, 0, + 0x01F1E6, 0x01F1F4, 0, + 0x01F1E6, 0x01F1F6, 0, + 0x01F1E6, 0x01F1F7, 0, + 0x01F1E6, 0x01F1F8, 0, + 0x01F1E6, 0x01F1F9, 0, + 0x01F1E6, 0x01F1FA, 0, + 0x01F1E6, 0x01F1FC, 0, + 0x01F1E6, 0x01F1FD, 0, + 0x01F1E6, 0x01F1FF, 0, + 0x01F1E7, 0x01F1E6, 0, + 0x01F1E7, 0x01F1E7, 0, + 0x01F1E7, 0x01F1E9, 0, + 0x01F1E7, 0x01F1EA, 0, + 0x01F1E7, 0x01F1EB, 0, + 0x01F1E7, 0x01F1EC, 0, + 0x01F1E7, 0x01F1ED, 0, + 0x01F1E7, 0x01F1EE, 0, + 0x01F1E7, 0x01F1EF, 0, + 0x01F1E7, 0x01F1F1, 0, + 0x01F1E7, 0x01F1F2, 0, + 0x01F1E7, 0x01F1F3, 0, + 0x01F1E7, 0x01F1F4, 0, + 0x01F1E7, 0x01F1F6, 0, + 0x01F1E7, 0x01F1F7, 0, + 0x01F1E7, 0x01F1F8, 0, + 0x01F1E7, 0x01F1F9, 0, + 0x01F1E7, 0x01F1FB, 0, + 0x01F1E7, 0x01F1FC, 0, + 0x01F1E7, 0x01F1FE, 0, + 0x01F1E7, 0x01F1FF, 0, + 0x01F1E8, 0x01F1E6, 0, + 0x01F1E8, 0x01F1E8, 0, + 0x01F1E8, 0x01F1E9, 0, + 0x01F1E8, 0x01F1EB, 0, + 0x01F1E8, 0x01F1EC, 0, + 0x01F1E8, 0x01F1ED, 0, + 0x01F1E8, 0x01F1EE, 0, + 0x01F1E8, 0x01F1F0, 0, + 0x01F1E8, 0x01F1F1, 0, + 0x01F1E8, 0x01F1F2, 0, + 0x01F1E8, 0x01F1F3, 0, + 0x01F1E8, 0x01F1F4, 0, + 0x01F1E8, 0x01F1F5, 0, + 0x01F1E8, 0x01F1F7, 0, + 0x01F1E8, 0x01F1FA, 0, + 0x01F1E8, 0x01F1FB, 0, + 0x01F1E8, 0x01F1FC, 0, + 0x01F1E8, 0x01F1FD, 0, + 0x01F1E8, 0x01F1FE, 0, + 0x01F1E8, 0x01F1FF, 0, + 0x01F1E9, 0x01F1EA, 0, + 0x01F1E9, 0x01F1EC, 0, + 0x01F1E9, 0x01F1EF, 0, + 0x01F1E9, 0x01F1F0, 0, + 0x01F1E9, 0x01F1F2, 0, + 0x01F1E9, 0x01F1F4, 0, + 0x01F1E9, 0x01F1FF, 0, + 0x01F1EA, 0x01F1E6, 0, + 0x01F1EA, 0x01F1E8, 0, + 0x01F1EA, 0x01F1EA, 0, + 0x01F1EA, 0x01F1EC, 0, + 0x01F1EA, 0x01F1ED, 0, + 0x01F1EA, 0x01F1F7, 0, + 0x01F1EA, 0x01F1F8, 0, + 0x01F1EA, 0x01F1F9, 0, + 0x01F1EA, 0x01F1FA, 0, + 0x01F1EB, 0x01F1EE, 0, + 0x01F1EB, 0x01F1EF, 0, + 0x01F1EB, 0x01F1F0, 0, + 0x01F1EB, 0x01F1F2, 0, + 0x01F1EB, 0x01F1F4, 0, + 0x01F1EB, 0x01F1F7, 0, + 0x01F1EC, 0x01F1E6, 0, + 0x01F1EC, 0x01F1E7, 0, + 0x01F1EC, 0x01F1E9, 0, + 0x01F1EC, 0x01F1EA, 0, + 0x01F1EC, 0x01F1EB, 0, + 0x01F1EC, 0x01F1EC, 0, + 0x01F1EC, 0x01F1ED, 0, + 0x01F1EC, 0x01F1EE, 0, + 0x01F1EC, 0x01F1F1, 0, + 0x01F1EC, 0x01F1F2, 0, + 0x01F1EC, 0x01F1F3, 0, + 0x01F1EC, 0x01F1F5, 0, + 0x01F1EC, 0x01F1F6, 0, + 0x01F1EC, 0x01F1F7, 0, + 0x01F1EC, 0x01F1F8, 0, + 0x01F1EC, 0x01F1F9, 0, + 0x01F1EC, 0x01F1FA, 0, + 0x01F1EC, 0x01F1FC, 0, + 0x01F1EC, 0x01F1FE, 0, + 0x01F1ED, 0x01F1F0, 0, + 0x01F1ED, 0x01F1F2, 0, + 0x01F1ED, 0x01F1F3, 0, + 0x01F1ED, 0x01F1F7, 0, + 0x01F1ED, 0x01F1F9, 0, + 0x01F1ED, 0x01F1FA, 0, + 0x01F1EE, 0x01F1E8, 0, + 0x01F1EE, 0x01F1E9, 0, + 0x01F1EE, 0x01F1EA, 0, + 0x01F1EE, 0x01F1F1, 0, + 0x01F1EE, 0x01F1F2, 0, + 0x01F1EE, 0x01F1F3, 0, + 0x01F1EE, 0x01F1F4, 0, + 0x01F1EE, 0x01F1F6, 0, + 0x01F1EE, 0x01F1F7, 0, + 0x01F1EE, 0x01F1F8, 0, + 0x01F1EE, 0x01F1F9, 0, + 0x01F1EF, 0x01F1EA, 0, + 0x01F1EF, 0x01F1F2, 0, + 0x01F1EF, 0x01F1F4, 0, + 0x01F1EF, 0x01F1F5, 0, + 0x01F1F0, 0x01F1EA, 0, + 0x01F1F0, 0x01F1EC, 0, + 0x01F1F0, 0x01F1ED, 0, + 0x01F1F0, 0x01F1EE, 0, + 0x01F1F0, 0x01F1F2, 0, + 0x01F1F0, 0x01F1F3, 0, + 0x01F1F0, 0x01F1F5, 0, + 0x01F1F0, 0x01F1F7, 0, + 0x01F1F0, 0x01F1FC, 0, + 0x01F1E6, 0x01F1E9, 0, + 0x01F1F0, 0x01F1FF, 0, + 0x01F1F1, 0x01F1E6, 0, + 0x01F1F1, 0x01F1E7, 0, + 0x01F1F1, 0x01F1E8, 0, + 0x01F1F1, 0x01F1EE, 0, + 0x01F1F1, 0x01F1F0, 0, + 0x01F1F1, 0x01F1F7, 0, + 0x01F1F1, 0x01F1F8, 0, + 0x01F1F1, 0x01F1F9, 0, + 0x01F1F1, 0x01F1FA, 0, + 0x01F1F1, 0x01F1FB, 0, + 0x01F1F1, 0x01F1FE, 0, + 0x01F1F2, 0x01F1E6, 0, + 0x01F1F2, 0x01F1E8, 0, + 0x01F1F2, 0x01F1E9, 0, + 0x01F1F2, 0x01F1EA, 0, + 0x01F1F2, 0x01F1EB, 0, + 0x01F1F2, 0x01F1EC, 0, + 0x01F1F2, 0x01F1ED, 0, + 0x01F1F2, 0x01F1F0, 0, + 0x01F1F2, 0x01F1F1, 0, + 0x01F1F2, 0x01F1F2, 0, + 0x01F1F2, 0x01F1F3, 0, + 0x01F1F2, 0x01F1F4, 0, + 0x01F1F2, 0x01F1F5, 0, + 0x01F1F2, 0x01F1F6, 0, + 0x01F1F2, 0x01F1F7, 0, + 0x01F1F2, 0x01F1F8, 0, + 0x01F1F2, 0x01F1F9, 0, + 0x01F1F2, 0x01F1FA, 0, + 0x01F1F2, 0x01F1FB, 0, + 0x01F1F2, 0x01F1FC, 0, + 0x01F1F2, 0x01F1FD, 0, + 0x01F1F2, 0x01F1FE, 0, + 0x01F1F2, 0x01F1FF, 0, + 0x01F1F3, 0x01F1E6, 0, + 0x01F1F3, 0x01F1E8, 0, + 0x01F1F3, 0x01F1EA, 0, + 0x01F1F3, 0x01F1EB, 0, + 0x01F1F3, 0x01F1EC, 0, + 0x01F1F3, 0x01F1EE, 0, + 0x01F1F3, 0x01F1F1, 0, + 0x01F1F3, 0x01F1F4, 0, + 0x01F1F3, 0x01F1F5, 0, + 0x01F1F3, 0x01F1F7, 0, + 0x01F1F3, 0x01F1FA, 0, + 0x01F1F3, 0x01F1FF, 0, + 0x01F1F4, 0x01F1F2, 0, + 0x01F1F5, 0x01F1E6, 0, + 0x01F1F5, 0x01F1EA, 0, + 0x01F1F5, 0x01F1EB, 0, + 0x01F1F5, 0x01F1EC, 0, + 0x01F1F5, 0x01F1ED, 0, + 0x01F1F5, 0x01F1F0, 0, + 0x01F1F5, 0x01F1F1, 0, + 0x01F1F5, 0x01F1F2, 0, + 0x01F1F5, 0x01F1F3, 0, + 0x01F1F5, 0x01F1F7, 0, + 0x01F1F5, 0x01F1F8, 0, + 0x01F1F5, 0x01F1F9, 0, + 0x01F1F5, 0x01F1FC, 0, + 0x01F1F5, 0x01F1FE, 0, + 0x01F1F6, 0x01F1E6, 0, + 0x01F1F7, 0x01F1EA, 0, + 0x01F1F7, 0x01F1F4, 0, + 0x01F1F7, 0x01F1F8, 0, + 0x01F1F7, 0x01F1FA, 0, + 0x01F1F7, 0x01F1FC, 0, + 0x01F1F8, 0x01F1E6, 0, + 0x01F1F8, 0x01F1E7, 0, + 0x01F1F8, 0x01F1E8, 0, + 0x01F1F8, 0x01F1E9, 0, + 0x01F1F8, 0x01F1EA, 0, + 0x01F1F8, 0x01F1EC, 0, + 0x01F1F8, 0x01F1ED, 0, + 0x01F1F8, 0x01F1EE, 0, + 0x01F1F8, 0x01F1EF, 0, + 0x01F1F8, 0x01F1F0, 0, + 0x01F1F8, 0x01F1F1, 0, + 0x01F1F8, 0x01F1F2, 0, + 0x01F1F8, 0x01F1F3, 0, + 0x01F1F8, 0x01F1F4, 0, + 0x01F1F8, 0x01F1F7, 0, + 0x01F1F8, 0x01F1F8, 0, + 0x01F1F8, 0x01F1F9, 0, + 0x01F1F8, 0x01F1FB, 0, + 0x01F1F8, 0x01F1FD, 0, + 0x01F1F8, 0x01F1FE, 0, + 0x01F1F8, 0x01F1FF, 0, + 0x01F1F9, 0x01F1E6, 0, + 0x01F1F9, 0x01F1E8, 0, + 0x01F1F9, 0x01F1E9, 0, + 0x01F1F9, 0x01F1EB, 0, + 0x01F1F9, 0x01F1EC, 0, + 0x01F1F9, 0x01F1ED, 0, + 0x01F1F9, 0x01F1EF, 0, + 0x01F1F9, 0x01F1F0, 0, + 0x01F1F9, 0x01F1F1, 0, + 0x01F1F9, 0x01F1F2, 0, + 0x01F1F9, 0x01F1F3, 0, + 0x01F1F9, 0x01F1F4, 0, + 0x01F1F9, 0x01F1F7, 0, + 0x01F1F9, 0x01F1F9, 0, + 0x01F1F9, 0x01F1FB, 0, + 0x01F1F9, 0x01F1FC, 0, + 0x01F1F9, 0x01F1FF, 0, + 0x01F1FA, 0x01F1E6, 0, + 0x01F1FA, 0x01F1EC, 0, + 0x01F1FA, 0x01F1F2, 0, + 0x01F1FA, 0x01F1F3, 0, + 0x01F1FA, 0x01F1F8, 0, + 0x01F1FA, 0x01F1FE, 0, + 0x01F1FA, 0x01F1FF, 0, + 0x01F1FB, 0x01F1E6, 0, + 0x01F1FB, 0x01F1E8, 0, + 0x01F1FB, 0x01F1EA, 0, + 0x01F1FB, 0x01F1EC, 0, + 0x01F1FB, 0x01F1EE, 0, + 0x01F1FB, 0x01F1F3, 0, + 0x01F1FB, 0x01F1FA, 0, + 0x01F1FC, 0x01F1EB, 0, + 0x01F1FC, 0x01F1F8, 0, + 0x01F1FD, 0x01F1F0, 0, + 0x01F1FE, 0x01F1EA, 0, + 0x01F1FE, 0x01F1F9, 0, + 0x01F1FF, 0x01F1E6, 0, + 0x01F1FF, 0x01F1F2, 0, + 0x01F1F0, 0x01F1FE, 0, + 0 // null-terminating the list +}; + +const base::uc32 UnicodePropertySequences::kEmojiTagSequences[] = { + 0x01F3F4, 0x0E0067, 0x0E0062, 0x0E0065, 0x0E006E, 0x0E0067, 0x0E007F, 0, + 0x01F3F4, 0x0E0067, 0x0E0062, 0x0E0073, 0x0E0063, 0x0E0074, 0x0E007F, 0, + 0x01F3F4, 0x0E0067, 0x0E0062, 0x0E0077, 0x0E006C, 0x0E0073, 0x0E007F, 0, + 0 // null-terminating the list +}; + +const base::uc32 UnicodePropertySequences::kEmojiZWJSequences[] = { + 0x01F468, 0x00200D, 0x002764, 0x00FE0F, 0x00200D, 0x01F468, 0, + 0x01F441, 0x00FE0F, 0x00200D, 0x01F5E8, 0x00FE0F, 0, + 0x01F468, 0x00200D, 0x01F466, 0, + 0x01F468, 0x00200D, 0x01F466, 0x00200D, 0x01F466, 0, + 0x01F468, 0x00200D, 0x01F467, 0, + 0x01F468, 0x00200D, 0x01F467, 0x00200D, 0x01F466, 0, + 0x01F468, 0x00200D, 0x01F467, 0x00200D, 0x01F467, 0, + 0x01F468, 0x00200D, 0x01F468, 0x00200D, 0x01F466, 0, + 0x01F468, 0x00200D, 0x01F468, 0x00200D, 0x01F466, 0x00200D, 0x01F466, 0, + 0x01F468, 0x00200D, 0x01F468, 0x00200D, 0x01F467, 0, + 0x01F468, 0x00200D, 0x01F468, 0x00200D, 0x01F467, 0x00200D, 0x01F466, 0, + 0x01F468, 0x00200D, 0x01F468, 0x00200D, 0x01F467, 0x00200D, 0x01F467, 0, + 0x01F468, 0x00200D, 0x01F469, 0x00200D, 0x01F466, 0, + 0x01F468, 0x00200D, 0x01F469, 0x00200D, 0x01F466, 0x00200D, 0x01F466, 0, + 0x01F468, 0x00200D, 0x01F469, 0x00200D, 0x01F467, 0, + 0x01F468, 0x00200D, 0x01F469, 0x00200D, 0x01F467, 0x00200D, 0x01F466, 0, + 0x01F468, 0x00200D, 0x01F469, 0x00200D, 0x01F467, 0x00200D, 0x01F467, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FB, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FB, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FC, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FB, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FC, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FD, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FB, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FC, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FD, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FE, 0, + 0x01F469, 0x00200D, 0x002764, 0x00FE0F, 0x00200D, 0x01F468, 0, + 0x01F469, 0x00200D, 0x002764, 0x00FE0F, 0x00200D, 0x01F469, 0, + 0x01F469, 0x00200D, 0x002764, 0x00FE0F, 0x00200D, 0x01F48B, 0x00200D, + 0x01F468, 0, + 0x01F469, 0x00200D, 0x002764, 0x00FE0F, 0x00200D, 0x01F48B, 0x00200D, + 0x01F469, 0, + 0x01F469, 0x00200D, 0x01F466, 0, + 0x01F469, 0x00200D, 0x01F466, 0x00200D, 0x01F466, 0, + 0x01F469, 0x00200D, 0x01F467, 0, + 0x01F469, 0x00200D, 0x01F467, 0x00200D, 0x01F466, 0, + 0x01F469, 0x00200D, 0x01F467, 0x00200D, 0x01F467, 0, + 0x01F469, 0x00200D, 0x01F469, 0x00200D, 0x01F466, 0, + 0x01F469, 0x00200D, 0x01F469, 0x00200D, 0x01F466, 0x00200D, 0x01F466, 0, + 0x01F469, 0x00200D, 0x01F469, 0x00200D, 0x01F467, 0, + 0x01F469, 0x00200D, 0x01F469, 0x00200D, 0x01F467, 0x00200D, 0x01F466, 0, + 0x01F469, 0x00200D, 0x01F469, 0x00200D, 0x01F467, 0x00200D, 0x01F467, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FC, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FD, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FE, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FF, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FB, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FD, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FE, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FF, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F91D, 0x00200D, 0x01F469, 0x01F3FB, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FB, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FC, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FE, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FF, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F91D, 0x00200D, 0x01F469, 0x01F3FB, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F91D, 0x00200D, 0x01F469, 0x01F3FC, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FB, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FC, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FD, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FF, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F91D, 0x00200D, 0x01F469, 0x01F3FB, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F91D, 0x00200D, 0x01F469, 0x01F3FC, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F91D, 0x00200D, 0x01F469, 0x01F3FD, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FB, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FC, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FD, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F91D, 0x00200D, 0x01F468, 0x01F3FE, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F91D, 0x00200D, 0x01F469, 0x01F3FB, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F91D, 0x00200D, 0x01F469, 0x01F3FC, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F91D, 0x00200D, 0x01F469, 0x01F3FD, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F91D, 0x00200D, 0x01F469, 0x01F3FE, 0, + 0x01F9D1, 0x00200D, 0x01F91D, 0x00200D, 0x01F9D1, 0, + 0x01F9D1, 0x01F3FB, 0x00200D, 0x01F91D, 0x00200D, 0x01F9D1, 0x01F3FB, 0, + 0x01F9D1, 0x01F3FC, 0x00200D, 0x01F91D, 0x00200D, 0x01F9D1, 0x01F3FB, 0, + 0x01F9D1, 0x01F3FC, 0x00200D, 0x01F91D, 0x00200D, 0x01F9D1, 0x01F3FC, 0, + 0x01F9D1, 0x01F3FD, 0x00200D, 0x01F91D, 0x00200D, 0x01F9D1, 0x01F3FB, 0, + 0x01F9D1, 0x01F3FD, 0x00200D, 0x01F91D, 0x00200D, 0x01F9D1, 0x01F3FC, 0, + 0x01F9D1, 0x01F3FD, 0x00200D, 0x01F91D, 0x00200D, 0x01F9D1, 0x01F3FD, 0, + 0x01F9D1, 0x01F3FE, 0x00200D, 0x01F91D, 0x00200D, 0x01F9D1, 0x01F3FB, 0, + 0x01F9D1, 0x01F3FE, 0x00200D, 0x01F91D, 0x00200D, 0x01F9D1, 0x01F3FC, 0, + 0x01F9D1, 0x01F3FE, 0x00200D, 0x01F91D, 0x00200D, 0x01F9D1, 0x01F3FD, 0, + 0x01F9D1, 0x01F3FE, 0x00200D, 0x01F91D, 0x00200D, 0x01F9D1, 0x01F3FE, 0, + 0x01F9D1, 0x01F3FF, 0x00200D, 0x01F91D, 0x00200D, 0x01F9D1, 0x01F3FB, 0, + 0x01F9D1, 0x01F3FF, 0x00200D, 0x01F91D, 0x00200D, 0x01F9D1, 0x01F3FC, 0, + 0x01F9D1, 0x01F3FF, 0x00200D, 0x01F91D, 0x00200D, 0x01F9D1, 0x01F3FD, 0, + 0x01F9D1, 0x01F3FF, 0x00200D, 0x01F91D, 0x00200D, 0x01F9D1, 0x01F3FE, 0, + 0x01F9D1, 0x01F3FF, 0x00200D, 0x01F91D, 0x00200D, 0x01F9D1, 0x01F3FF, 0, + 0x01F468, 0x00200D, 0x002695, 0x00FE0F, 0, + 0x01F468, 0x00200D, 0x002696, 0x00FE0F, 0, + 0x01F468, 0x00200D, 0x002708, 0x00FE0F, 0, + 0x01F468, 0x00200D, 0x01F33E, 0, + 0x01F468, 0x00200D, 0x01F373, 0, + 0x01F468, 0x00200D, 0x01F393, 0, + 0x01F468, 0x00200D, 0x01F3A4, 0, + 0x01F468, 0x00200D, 0x01F3A8, 0, + 0x01F468, 0x00200D, 0x01F3EB, 0, + 0x01F468, 0x00200D, 0x01F3ED, 0, + 0x01F468, 0x00200D, 0x01F4BB, 0, + 0x01F468, 0x00200D, 0x01F4BC, 0, + 0x01F468, 0x00200D, 0x01F527, 0, + 0x01F468, 0x00200D, 0x01F52C, 0, + 0x01F468, 0x00200D, 0x01F680, 0, + 0x01F468, 0x00200D, 0x01F692, 0, + 0x01F468, 0x00200D, 0x01F9AF, 0, + 0x01F468, 0x00200D, 0x01F9BC, 0, + 0x01F468, 0x00200D, 0x01F9BD, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x002695, 0x00FE0F, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x002696, 0x00FE0F, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x002708, 0x00FE0F, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F33E, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F373, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F393, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F3A4, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F3A8, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F3EB, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F3ED, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F4BB, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F4BC, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F527, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F52C, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F680, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F692, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F9AF, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F9BC, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F9BD, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x002695, 0x00FE0F, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x002696, 0x00FE0F, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x002708, 0x00FE0F, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F33E, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F373, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F393, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F3A4, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F3A8, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F3EB, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F3ED, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F4BB, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F4BC, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F527, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F52C, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F680, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F692, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F9AF, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F9BC, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F9BD, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x002695, 0x00FE0F, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x002696, 0x00FE0F, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x002708, 0x00FE0F, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F33E, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F373, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F393, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F3A4, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F3A8, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F3EB, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F3ED, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F4BB, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F4BC, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F527, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F52C, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F680, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F692, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F9AF, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F9BC, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F9BD, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x002695, 0x00FE0F, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x002696, 0x00FE0F, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x002708, 0x00FE0F, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F33E, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F373, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F393, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F3A4, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F3A8, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F3EB, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F3ED, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F4BB, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F4BC, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F527, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F52C, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F680, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F692, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F9AF, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F9BC, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F9BD, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x002695, 0x00FE0F, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x002696, 0x00FE0F, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x002708, 0x00FE0F, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F33E, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F373, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F393, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F3A4, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F3A8, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F3EB, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F3ED, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F4BB, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F4BC, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F527, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F52C, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F680, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F692, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F9AF, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F9BC, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F9BD, 0, + 0x01F469, 0x00200D, 0x002695, 0x00FE0F, 0, + 0x01F469, 0x00200D, 0x002696, 0x00FE0F, 0, + 0x01F469, 0x00200D, 0x002708, 0x00FE0F, 0, + 0x01F469, 0x00200D, 0x01F33E, 0, + 0x01F469, 0x00200D, 0x01F373, 0, + 0x01F469, 0x00200D, 0x01F393, 0, + 0x01F469, 0x00200D, 0x01F3A4, 0, + 0x01F469, 0x00200D, 0x01F3A8, 0, + 0x01F469, 0x00200D, 0x01F3EB, 0, + 0x01F469, 0x00200D, 0x01F3ED, 0, + 0x01F469, 0x00200D, 0x01F4BB, 0, + 0x01F469, 0x00200D, 0x01F4BC, 0, + 0x01F469, 0x00200D, 0x01F527, 0, + 0x01F469, 0x00200D, 0x01F52C, 0, + 0x01F469, 0x00200D, 0x01F680, 0, + 0x01F469, 0x00200D, 0x01F692, 0, + 0x01F469, 0x00200D, 0x01F9AF, 0, + 0x01F469, 0x00200D, 0x01F9BC, 0, + 0x01F469, 0x00200D, 0x01F9BD, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x002695, 0x00FE0F, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x002696, 0x00FE0F, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x002708, 0x00FE0F, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F33E, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F373, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F393, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F3A4, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F3A8, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F3EB, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F3ED, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F4BB, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F4BC, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F527, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F52C, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F680, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F692, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F9AF, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F9BC, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F9BD, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x002695, 0x00FE0F, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x002696, 0x00FE0F, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x002708, 0x00FE0F, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F33E, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F373, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F393, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F3A4, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F3A8, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F3EB, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F3ED, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F4BB, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F4BC, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F527, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F52C, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F680, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F692, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F9AF, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F9BC, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F9BD, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x002695, 0x00FE0F, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x002696, 0x00FE0F, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x002708, 0x00FE0F, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F33E, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F373, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F393, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F3A4, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F3A8, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F3EB, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F3ED, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F4BB, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F4BC, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F527, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F52C, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F680, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F692, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F9AF, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F9BC, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F9BD, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x002695, 0x00FE0F, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x002696, 0x00FE0F, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x002708, 0x00FE0F, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F33E, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F373, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F393, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F3A4, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F3A8, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F3EB, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F3ED, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F4BB, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F4BC, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F527, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F52C, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F680, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F692, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F9AF, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F9BC, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F9BD, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x002695, 0x00FE0F, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x002696, 0x00FE0F, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x002708, 0x00FE0F, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F33E, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F373, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F393, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F3A4, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F3A8, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F3EB, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F3ED, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F4BB, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F4BC, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F527, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F52C, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F680, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F692, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F9AF, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F9BC, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F9BD, 0, + 0x0026F9, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x0026F9, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x0026F9, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x0026F9, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x0026F9, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x0026F9, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x0026F9, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x0026F9, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x0026F9, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x0026F9, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x0026F9, 0x00FE0F, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x0026F9, 0x00FE0F, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3C3, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3C3, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3C3, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3C3, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3C3, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3C3, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3C3, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3C3, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3C3, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3C3, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3C3, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3C3, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3C4, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3C4, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3C4, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3C4, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3C4, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3C4, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3C4, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3C4, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3C4, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3C4, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3C4, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3C4, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CA, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CA, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CA, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CA, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CA, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CA, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CA, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CA, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CA, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CA, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CA, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CA, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CB, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CB, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CB, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CB, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CB, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CB, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CB, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CB, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CB, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CB, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CB, 0x00FE0F, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CB, 0x00FE0F, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CC, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CC, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CC, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CC, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CC, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CC, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CC, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CC, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CC, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CC, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F3CC, 0x00FE0F, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F3CC, 0x00FE0F, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F46E, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F46E, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F46E, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F46E, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F46E, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F46E, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F46E, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F46E, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F46E, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F46E, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F46E, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F46E, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F46F, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F46F, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F471, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F471, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F471, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F471, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F471, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F471, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F471, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F471, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F471, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F471, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F471, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F471, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F473, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F473, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F473, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F473, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F473, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F473, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F473, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F473, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F473, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F473, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F473, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F473, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F477, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F477, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F477, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F477, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F477, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F477, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F477, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F477, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F477, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F477, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F477, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F477, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F481, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F481, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F481, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F481, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F481, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F481, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F481, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F481, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F481, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F481, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F481, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F481, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F482, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F482, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F468, 0x00200D, 0x002764, 0x00FE0F, 0x00200D, 0x01F48B, 0x00200D, + 0x01F468, 0, + 0x01F482, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F482, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F482, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F482, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F482, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F482, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F482, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F482, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F482, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F486, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F486, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F486, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F486, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F486, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F486, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F486, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F486, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F486, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F486, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F486, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F486, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F487, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F487, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F487, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F487, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F487, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F487, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F487, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F487, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F487, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F487, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F487, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F487, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F575, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F575, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F575, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F575, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F575, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F575, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F575, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F575, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F575, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F575, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F575, 0x00FE0F, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F575, 0x00FE0F, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F645, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F645, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F645, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F645, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F645, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F645, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F645, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F645, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F645, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F645, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F645, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F645, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F646, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F646, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F646, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F646, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F646, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F646, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F646, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F646, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F646, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F646, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F646, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F646, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F647, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F647, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F647, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F647, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F647, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F647, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F647, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F647, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F647, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F647, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F647, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F647, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64B, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64B, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64B, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64B, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64B, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64B, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64B, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64B, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64B, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64B, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64B, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64B, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64D, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64D, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64D, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64D, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64D, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64D, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64D, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64D, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64D, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64D, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64D, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64D, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64E, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64E, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64E, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64E, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64E, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64E, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64E, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64E, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64E, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64E, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F64E, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F64E, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6A3, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6A3, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6A3, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6A3, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6A3, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6A3, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6A3, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6A3, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6A3, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6A3, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6A3, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6A3, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B4, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B4, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B4, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B4, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B4, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B4, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B4, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B4, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B4, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B4, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B4, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B4, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B5, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B5, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B5, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B5, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B5, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B5, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B5, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B5, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B5, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B5, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B5, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B5, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B6, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B6, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B6, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B6, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B6, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B6, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B6, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B6, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B6, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B6, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F6B6, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F6B6, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F926, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F926, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F926, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F926, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F926, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F926, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F926, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F926, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F926, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F926, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F926, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F926, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F937, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F937, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F937, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F937, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F937, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F937, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F937, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F937, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F937, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F937, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F937, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F937, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F938, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F938, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F938, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F938, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F938, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F938, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F938, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F938, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F938, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F938, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F938, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F938, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F939, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F939, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F939, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F939, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F939, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F939, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F939, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F939, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F939, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F939, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F939, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F939, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F93C, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F93C, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F93D, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F93D, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F93D, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F93D, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F93D, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F93D, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F93D, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F93D, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F93D, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F93D, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F93D, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F93D, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F93E, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F93E, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F93E, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F93E, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F93E, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F93E, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F93E, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F93E, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F93E, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F93E, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F93E, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F93E, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9B8, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9B8, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9B8, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9B8, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9B8, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9B8, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9B8, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9B8, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9B8, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9B8, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9B8, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9B8, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9B9, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9B9, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9B9, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9B9, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9B9, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9B9, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9B9, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9B9, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9B9, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9B9, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9B9, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9B9, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CD, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CD, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CD, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CD, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CD, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CD, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CD, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CD, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CD, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CD, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CE, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CE, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CE, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CE, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CE, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CE, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CE, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CE, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CE, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CE, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CF, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CF, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CF, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CF, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CF, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CF, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CF, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CF, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9CF, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9CF, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D6, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D6, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D6, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D6, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D6, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D6, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D6, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D6, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D6, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D6, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D6, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D6, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D7, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D7, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D7, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D7, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D7, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D7, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D7, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D7, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D7, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D7, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D7, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D7, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D8, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D8, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D8, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D8, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D8, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D8, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D8, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D8, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D8, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D8, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D8, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D8, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D9, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D9, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D9, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D9, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D9, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D9, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D9, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D9, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D9, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D9, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9D9, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9D9, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DA, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DA, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DA, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DA, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DA, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DA, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DA, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DA, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DA, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DA, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DA, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DA, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DB, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DB, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DB, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DB, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DB, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DB, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DB, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DB, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DB, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DB, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DC, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DC, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DC, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DC, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DC, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DC, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DC, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DC, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DC, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DC, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DD, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DD, 0x01F3FB, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DD, 0x01F3FC, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DD, 0x01F3FC, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DD, 0x01F3FD, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DD, 0x01F3FD, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DD, 0x01F3FE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DD, 0x01F3FE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DD, 0x01F3FF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DD, 0x01F3FF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DE, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DE, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F9DF, 0x00200D, 0x002640, 0x00FE0F, 0, + 0x01F9DF, 0x00200D, 0x002642, 0x00FE0F, 0, + 0x01F468, 0x00200D, 0x01F9B0, 0, + 0x01F468, 0x00200D, 0x01F9B1, 0, + 0x01F468, 0x00200D, 0x01F9B2, 0, + 0x01F468, 0x00200D, 0x01F9B3, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F9B0, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F9B1, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F9B2, 0, + 0x01F468, 0x01F3FB, 0x00200D, 0x01F9B3, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F9B0, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F9B1, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F9B2, 0, + 0x01F468, 0x01F3FC, 0x00200D, 0x01F9B3, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F9B0, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F9B1, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F9B2, 0, + 0x01F468, 0x01F3FD, 0x00200D, 0x01F9B3, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F9B0, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F9B1, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F9B2, 0, + 0x01F468, 0x01F3FE, 0x00200D, 0x01F9B3, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F9B0, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F9B1, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F9B2, 0, + 0x01F468, 0x01F3FF, 0x00200D, 0x01F9B3, 0, + 0x01F469, 0x00200D, 0x01F9B0, 0, + 0x01F469, 0x00200D, 0x01F9B1, 0, + 0x01F469, 0x00200D, 0x01F9B2, 0, + 0x01F469, 0x00200D, 0x01F9B3, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F9B0, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F9B1, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F9B2, 0, + 0x01F469, 0x01F3FB, 0x00200D, 0x01F9B3, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F9B0, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F9B1, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F9B2, 0, + 0x01F469, 0x01F3FC, 0x00200D, 0x01F9B3, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F9B0, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F9B1, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F9B2, 0, + 0x01F469, 0x01F3FD, 0x00200D, 0x01F9B3, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F9B0, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F9B1, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F9B2, 0, + 0x01F469, 0x01F3FE, 0x00200D, 0x01F9B3, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F9B0, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F9B1, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F9B2, 0, + 0x01F469, 0x01F3FF, 0x00200D, 0x01F9B3, 0, + 0x01F3F3, 0x00FE0F, 0x00200D, 0x01F308, 0, + 0x01F3F4, 0x00200D, 0x002620, 0x00FE0F, 0, + 0x01F415, 0x00200D, 0x01F9BA, 0, + 0x01F482, 0x01F3FB, 0x00200D, 0x002640, 0x00FE0F, 0, + 0 // null-terminating the list +}; +// clang-format on + +} // namespace internal +} // namespace v8 + +#endif // V8_INTL_SUPPORT diff --git a/js/src/irregexp/imported/property-sequences.h b/js/src/irregexp/imported/property-sequences.h new file mode 100644 index 0000000000..9b3a188865 --- /dev/null +++ b/js/src/irregexp/imported/property-sequences.h @@ -0,0 +1,27 @@ +// Copyright 2018 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. + +#ifndef V8_REGEXP_PROPERTY_SEQUENCES_H_ +#define V8_REGEXP_PROPERTY_SEQUENCES_H_ + +#ifdef V8_INTL_SUPPORT + +#include "irregexp/RegExpShim.h" + +namespace v8 { +namespace internal { + +class UnicodePropertySequences : public AllStatic { + public: + static const base::uc32 kEmojiFlagSequences[]; + static const base::uc32 kEmojiTagSequences[]; + static const base::uc32 kEmojiZWJSequences[]; +}; + +} // namespace internal +} // namespace v8 + +#endif // V8_INTL_SUPPORT + +#endif // V8_REGEXP_PROPERTY_SEQUENCES_H_ diff --git a/js/src/irregexp/imported/regexp-ast.cc b/js/src/irregexp/imported/regexp-ast.cc new file mode 100644 index 0000000000..63eeb5c05d --- /dev/null +++ b/js/src/irregexp/imported/regexp-ast.cc @@ -0,0 +1,432 @@ +// Copyright 2016 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-ast.h" + + +namespace v8 { +namespace internal { + +#define MAKE_ACCEPT(Name) \ + void* RegExp##Name::Accept(RegExpVisitor* visitor, void* data) { \ + return visitor->Visit##Name(this, data); \ + } +FOR_EACH_REG_EXP_TREE_TYPE(MAKE_ACCEPT) +#undef MAKE_ACCEPT + +#define MAKE_TYPE_CASE(Name) \ + RegExp##Name* RegExpTree::As##Name() { return nullptr; } \ + bool RegExpTree::Is##Name() { return false; } +FOR_EACH_REG_EXP_TREE_TYPE(MAKE_TYPE_CASE) +#undef MAKE_TYPE_CASE + +#define MAKE_TYPE_CASE(Name) \ + RegExp##Name* RegExp##Name::As##Name() { return this; } \ + bool RegExp##Name::Is##Name() { return true; } +FOR_EACH_REG_EXP_TREE_TYPE(MAKE_TYPE_CASE) +#undef MAKE_TYPE_CASE + +namespace { + +Interval ListCaptureRegisters(ZoneList<RegExpTree*>* children) { + Interval result = Interval::Empty(); + for (int i = 0; i < children->length(); i++) + result = result.Union(children->at(i)->CaptureRegisters()); + return result; +} + +} // namespace + +Interval RegExpAlternative::CaptureRegisters() { + return ListCaptureRegisters(nodes()); +} + + +Interval RegExpDisjunction::CaptureRegisters() { + return ListCaptureRegisters(alternatives()); +} + + +Interval RegExpLookaround::CaptureRegisters() { + return body()->CaptureRegisters(); +} + + +Interval RegExpCapture::CaptureRegisters() { + Interval self(StartRegister(index()), EndRegister(index())); + return self.Union(body()->CaptureRegisters()); +} + + +Interval RegExpQuantifier::CaptureRegisters() { + return body()->CaptureRegisters(); +} + + +bool RegExpAssertion::IsAnchoredAtStart() { + return assertion_type() == RegExpAssertion::Type::START_OF_INPUT; +} + + +bool RegExpAssertion::IsAnchoredAtEnd() { + return assertion_type() == RegExpAssertion::Type::END_OF_INPUT; +} + + +bool RegExpAlternative::IsAnchoredAtStart() { + ZoneList<RegExpTree*>* nodes = this->nodes(); + for (int i = 0; i < nodes->length(); i++) { + RegExpTree* node = nodes->at(i); + if (node->IsAnchoredAtStart()) { + return true; + } + if (node->max_match() > 0) { + return false; + } + } + return false; +} + + +bool RegExpAlternative::IsAnchoredAtEnd() { + ZoneList<RegExpTree*>* nodes = this->nodes(); + for (int i = nodes->length() - 1; i >= 0; i--) { + RegExpTree* node = nodes->at(i); + if (node->IsAnchoredAtEnd()) { + return true; + } + if (node->max_match() > 0) { + return false; + } + } + return false; +} + + +bool RegExpDisjunction::IsAnchoredAtStart() { + ZoneList<RegExpTree*>* alternatives = this->alternatives(); + for (int i = 0; i < alternatives->length(); i++) { + if (!alternatives->at(i)->IsAnchoredAtStart()) return false; + } + return true; +} + + +bool RegExpDisjunction::IsAnchoredAtEnd() { + ZoneList<RegExpTree*>* alternatives = this->alternatives(); + for (int i = 0; i < alternatives->length(); i++) { + if (!alternatives->at(i)->IsAnchoredAtEnd()) return false; + } + return true; +} + + +bool RegExpLookaround::IsAnchoredAtStart() { + return is_positive() && type() == LOOKAHEAD && body()->IsAnchoredAtStart(); +} + + +bool RegExpCapture::IsAnchoredAtStart() { return body()->IsAnchoredAtStart(); } + + +bool RegExpCapture::IsAnchoredAtEnd() { return body()->IsAnchoredAtEnd(); } + +namespace { + +// Convert regular expression trees to a simple sexp representation. +// This representation should be different from the input grammar +// in as many cases as possible, to make it more difficult for incorrect +// parses to look as correct ones which is likely if the input and +// output formats are alike. +class RegExpUnparser final : public RegExpVisitor { + public: + RegExpUnparser(std::ostream& os, Zone* zone) : os_(os), zone_(zone) {} + void VisitCharacterRange(CharacterRange that); +#define MAKE_CASE(Name) void* Visit##Name(RegExp##Name*, void* data) override; + FOR_EACH_REG_EXP_TREE_TYPE(MAKE_CASE) +#undef MAKE_CASE + private: + std::ostream& os_; + Zone* zone_; +}; + +} // namespace + +void* RegExpUnparser::VisitDisjunction(RegExpDisjunction* that, void* data) { + os_ << "(|"; + for (int i = 0; i < that->alternatives()->length(); i++) { + os_ << " "; + that->alternatives()->at(i)->Accept(this, data); + } + os_ << ")"; + return nullptr; +} + + +void* RegExpUnparser::VisitAlternative(RegExpAlternative* that, void* data) { + os_ << "(:"; + for (int i = 0; i < that->nodes()->length(); i++) { + os_ << " "; + that->nodes()->at(i)->Accept(this, data); + } + os_ << ")"; + return nullptr; +} + + +void RegExpUnparser::VisitCharacterRange(CharacterRange that) { + os_ << AsUC32(that.from()); + if (!that.IsSingleton()) { + os_ << "-" << AsUC32(that.to()); + } +} + +void* RegExpUnparser::VisitClassRanges(RegExpClassRanges* that, void* data) { + if (that->is_negated()) os_ << "^"; + os_ << "["; + for (int i = 0; i < that->ranges(zone_)->length(); i++) { + if (i > 0) os_ << " "; + VisitCharacterRange(that->ranges(zone_)->at(i)); + } + os_ << "]"; + return nullptr; +} + +void* RegExpUnparser::VisitClassSetOperand(RegExpClassSetOperand* that, + void* data) { + os_ << "!["; + for (int i = 0; i < that->ranges()->length(); i++) { + if (i > 0) os_ << " "; + VisitCharacterRange(that->ranges()->at(i)); + } + if (that->has_strings()) { + for (auto iter : *that->strings()) { + os_ << " '"; + os_ << std::string(iter.first.begin(), iter.first.end()); + os_ << "'"; + } + } + os_ << "]"; + return nullptr; +} + +void* RegExpUnparser::VisitClassSetExpression(RegExpClassSetExpression* that, + void* data) { + switch (that->operation()) { + case RegExpClassSetExpression::OperationType::kUnion: + os_ << "++"; + break; + case RegExpClassSetExpression::OperationType::kIntersection: + os_ << "&&"; + break; + case RegExpClassSetExpression::OperationType::kSubtraction: + os_ << "--"; + break; + } + if (that->is_negated()) os_ << "^"; + os_ << "["; + for (int i = 0; i < that->operands()->length(); i++) { + if (i > 0) os_ << " "; + that->operands()->at(i)->Accept(this, data); + } + os_ << "]"; + return nullptr; +} + +void* RegExpUnparser::VisitAssertion(RegExpAssertion* that, void* data) { + switch (that->assertion_type()) { + case RegExpAssertion::Type::START_OF_INPUT: + os_ << "@^i"; + break; + case RegExpAssertion::Type::END_OF_INPUT: + os_ << "@$i"; + break; + case RegExpAssertion::Type::START_OF_LINE: + os_ << "@^l"; + break; + case RegExpAssertion::Type::END_OF_LINE: + os_ << "@$l"; + break; + case RegExpAssertion::Type::BOUNDARY: + os_ << "@b"; + break; + case RegExpAssertion::Type::NON_BOUNDARY: + os_ << "@B"; + break; + } + return nullptr; +} + + +void* RegExpUnparser::VisitAtom(RegExpAtom* that, void* data) { + os_ << "'"; + base::Vector<const base::uc16> chardata = that->data(); + for (int i = 0; i < chardata.length(); i++) { + os_ << AsUC16(chardata[i]); + } + os_ << "'"; + return nullptr; +} + + +void* RegExpUnparser::VisitText(RegExpText* that, void* data) { + if (that->elements()->length() == 1) { + that->elements()->at(0).tree()->Accept(this, data); + } else { + os_ << "(!"; + for (int i = 0; i < that->elements()->length(); i++) { + os_ << " "; + that->elements()->at(i).tree()->Accept(this, data); + } + os_ << ")"; + } + return nullptr; +} + + +void* RegExpUnparser::VisitQuantifier(RegExpQuantifier* that, void* data) { + os_ << "(# " << that->min() << " "; + if (that->max() == RegExpTree::kInfinity) { + os_ << "- "; + } else { + os_ << that->max() << " "; + } + os_ << (that->is_greedy() ? "g " : that->is_possessive() ? "p " : "n "); + that->body()->Accept(this, data); + os_ << ")"; + return nullptr; +} + + +void* RegExpUnparser::VisitCapture(RegExpCapture* that, void* data) { + os_ << "(^ "; + that->body()->Accept(this, data); + os_ << ")"; + return nullptr; +} + +void* RegExpUnparser::VisitGroup(RegExpGroup* that, void* data) { + os_ << "(?: "; + that->body()->Accept(this, data); + os_ << ")"; + return nullptr; +} + +void* RegExpUnparser::VisitLookaround(RegExpLookaround* that, void* data) { + os_ << "("; + os_ << (that->type() == RegExpLookaround::LOOKAHEAD ? "->" : "<-"); + os_ << (that->is_positive() ? " + " : " - "); + that->body()->Accept(this, data); + os_ << ")"; + return nullptr; +} + + +void* RegExpUnparser::VisitBackReference(RegExpBackReference* that, + void* data) { + os_ << "(<- " << that->index() << ")"; + return nullptr; +} + + +void* RegExpUnparser::VisitEmpty(RegExpEmpty* that, void* data) { + os_ << '%'; + return nullptr; +} + +std::ostream& RegExpTree::Print(std::ostream& os, Zone* zone) { + RegExpUnparser unparser(os, zone); + Accept(&unparser, nullptr); + return os; +} + +RegExpDisjunction::RegExpDisjunction(ZoneList<RegExpTree*>* alternatives) + : alternatives_(alternatives) { + DCHECK_LT(1, alternatives->length()); + RegExpTree* first_alternative = alternatives->at(0); + min_match_ = first_alternative->min_match(); + max_match_ = first_alternative->max_match(); + for (int i = 1; i < alternatives->length(); i++) { + RegExpTree* alternative = alternatives->at(i); + min_match_ = std::min(min_match_, alternative->min_match()); + max_match_ = std::max(max_match_, alternative->max_match()); + } +} + +namespace { + +int IncreaseBy(int previous, int increase) { + if (RegExpTree::kInfinity - previous < increase) { + return RegExpTree::kInfinity; + } else { + return previous + increase; + } +} + +} // namespace + +RegExpAlternative::RegExpAlternative(ZoneList<RegExpTree*>* nodes) + : nodes_(nodes) { + DCHECK_LT(1, nodes->length()); + min_match_ = 0; + max_match_ = 0; + for (int i = 0; i < nodes->length(); i++) { + RegExpTree* node = nodes->at(i); + int node_min_match = node->min_match(); + min_match_ = IncreaseBy(min_match_, node_min_match); + int node_max_match = node->max_match(); + max_match_ = IncreaseBy(max_match_, node_max_match); + } +} + +RegExpClassSetOperand::RegExpClassSetOperand(ZoneList<CharacterRange>* ranges, + CharacterClassStrings* strings) + : ranges_(ranges), strings_(strings) { + DCHECK_NOT_NULL(ranges); + min_match_ = 0; + max_match_ = 0; + if (!ranges->is_empty()) { + min_match_ = 1; + max_match_ = 2; + } + if (has_strings()) { + for (auto string : *strings) { + min_match_ = std::min(min_match_, string.second->min_match()); + max_match_ = std::max(max_match_, string.second->max_match()); + } + } +} + +RegExpClassSetExpression::RegExpClassSetExpression( + OperationType op, bool is_negated, bool may_contain_strings, + ZoneList<RegExpTree*>* operands) + : 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()); + } +} + +// static +RegExpClassSetExpression* RegExpClassSetExpression::Empty(Zone* zone, + bool is_negated) { + ZoneList<CharacterRange>* ranges = + zone->template New<ZoneList<CharacterRange>>(0, zone); + RegExpClassSetOperand* op = + zone->template New<RegExpClassSetOperand>(ranges, nullptr); + ZoneList<RegExpTree*>* operands = + zone->template New<ZoneList<RegExpTree*>>(1, zone); + operands->Add(op, zone); + return zone->template New<RegExpClassSetExpression>( + RegExpClassSetExpression::OperationType::kUnion, is_negated, false, + operands); +} + +} // namespace internal +} // namespace v8 diff --git a/js/src/irregexp/imported/regexp-ast.h b/js/src/irregexp/imported/regexp-ast.h new file mode 100644 index 0000000000..af90b1dda3 --- /dev/null +++ b/js/src/irregexp/imported/regexp-ast.h @@ -0,0 +1,735 @@ +// Copyright 2016 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. + +#ifndef V8_REGEXP_REGEXP_AST_H_ +#define V8_REGEXP_REGEXP_AST_H_ + +#ifdef V8_INTL_SUPPORT +#include "unicode/uniset.h" +#endif // V8_INTL_SUPPORT +#include "irregexp/RegExpShim.h" + +namespace v8 { +namespace internal { + +#define FOR_EACH_REG_EXP_TREE_TYPE(VISIT) \ + VISIT(Disjunction) \ + VISIT(Alternative) \ + VISIT(Assertion) \ + VISIT(ClassRanges) \ + VISIT(ClassSetOperand) \ + VISIT(ClassSetExpression) \ + VISIT(Atom) \ + VISIT(Quantifier) \ + VISIT(Capture) \ + VISIT(Group) \ + VISIT(Lookaround) \ + VISIT(BackReference) \ + VISIT(Empty) \ + VISIT(Text) + +#define FORWARD_DECLARE(Name) class RegExp##Name; +FOR_EACH_REG_EXP_TREE_TYPE(FORWARD_DECLARE) +#undef FORWARD_DECLARE + +class RegExpCompiler; +class RegExpNode; +class RegExpTree; + +class RegExpVisitor { + public: + virtual ~RegExpVisitor() = default; +#define MAKE_CASE(Name) \ + virtual void* Visit##Name(RegExp##Name*, void* data) = 0; + FOR_EACH_REG_EXP_TREE_TYPE(MAKE_CASE) +#undef MAKE_CASE +}; + +// A simple closed interval. +class Interval { + public: + Interval() : from_(kNone), to_(kNone - 1) {} // '- 1' for branchless size(). + Interval(int from, int to) : from_(from), to_(to) {} + Interval Union(Interval that) { + if (that.from_ == kNone) return *this; + if (from_ == kNone) return that; + return Interval(std::min(from_, that.from_), std::max(to_, that.to_)); + } + + static Interval Empty() { return Interval(); } + + bool Contains(int value) const { return (from_ <= value) && (value <= to_); } + bool is_empty() const { return from_ == kNone; } + int from() const { return from_; } + int to() const { return to_; } + int size() const { return to_ - from_ + 1; } + + static constexpr int kNone = -1; + + private: + int from_; + int to_; +}; + +// Named standard character sets. +enum class StandardCharacterSet : char { + kWhitespace = 's', // Like /\s/. + kNotWhitespace = 'S', // Like /\S/. + kWord = 'w', // Like /\w/. + kNotWord = 'W', // Like /\W/. + kDigit = 'd', // Like /\d/. + kNotDigit = 'D', // Like /\D/. + kLineTerminator = 'n', // The inverse of /./. + kNotLineTerminator = '.', // Like /./. + kEverything = '*', // Matches every character, like /./s. +}; + +// Represents code points (with values up to 0x10FFFF) in the range from from_ +// to to_, both ends are inclusive. +class CharacterRange { + public: + CharacterRange() = default; + // For compatibility with the CHECK_OK macro. + CharacterRange(void* null) { DCHECK_NULL(null); } // NOLINT + + static inline CharacterRange Singleton(base::uc32 value) { + return CharacterRange(value, value); + } + static inline CharacterRange Range(base::uc32 from, base::uc32 to) { + DCHECK(0 <= from && to <= kMaxCodePoint); + DCHECK(static_cast<uint32_t>(from) <= static_cast<uint32_t>(to)); + return CharacterRange(from, to); + } + static inline CharacterRange Everything() { + return CharacterRange(0, kMaxCodePoint); + } + + static inline ZoneList<CharacterRange>* List(Zone* zone, + CharacterRange range) { + ZoneList<CharacterRange>* list = + zone->New<ZoneList<CharacterRange>>(1, zone); + list->Add(range, zone); + return list; + } + + // Add class escapes. Add case equivalent closure for \w and \W if necessary. + V8_EXPORT_PRIVATE static void AddClassEscape( + StandardCharacterSet standard_character_set, + ZoneList<CharacterRange>* ranges, bool add_unicode_case_equivalents, + Zone* zone); + // Add case equivalents to ranges. Only used for /i, not for /ui or /vi, as + // the semantics for unicode mode are slightly different. + // See https://tc39.es/ecma262/#sec-runtime-semantics-canonicalize-ch Note 4. + V8_EXPORT_PRIVATE static void AddCaseEquivalents( + Isolate* isolate, Zone* zone, ZoneList<CharacterRange>* ranges, + bool is_one_byte); + // Add case equivalent code points to ranges. Only used for /ui and /vi, not + // 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<CharacterRange>* 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_; } + + // Whether a range list is in canonical form: Ranges ordered by from value, + // and ranges non-overlapping and non-adjacent. + V8_EXPORT_PRIVATE static bool IsCanonical( + const ZoneList<CharacterRange>* ranges); + // Convert range list to canonical form. The characters covered by the ranges + // will still be the same, but no character is in more than one range, and + // adjacent ranges are merged. The resulting list may be shorter than the + // original, but cannot be longer. + static void Canonicalize(ZoneList<CharacterRange>* ranges); + // Negate the contents of a character range in canonical form. + static void Negate(const ZoneList<CharacterRange>* src, + ZoneList<CharacterRange>* dst, Zone* zone); + // Intersect the contents of two character ranges in canonical form. + static void Intersect(const ZoneList<CharacterRange>* lhs, + const ZoneList<CharacterRange>* rhs, + ZoneList<CharacterRange>* dst, Zone* zone); + // Subtract the contents of |to_remove| from the contents of |src|. + static void Subtract(const ZoneList<CharacterRange>* src, + const ZoneList<CharacterRange>* to_remove, + ZoneList<CharacterRange>* dst, Zone* zone); + // Remove all ranges outside the one-byte range. + static void ClampToOneByte(ZoneList<CharacterRange>* ranges); + // Checks if two ranges (both need to be canonical) are equal. + static bool Equals(const ZoneList<CharacterRange>* lhs, + const ZoneList<CharacterRange>* rhs); + + private: + CharacterRange(base::uc32 from, base::uc32 to) : from_(from), to_(to) {} + + static constexpr int kMaxCodePoint = 0x10ffff; + + base::uc32 from_ = 0; + base::uc32 to_ = 0; +}; + +inline bool operator==(const CharacterRange& lhs, const CharacterRange& rhs) { + return lhs.from() == rhs.from() && lhs.to() == rhs.to(); +} +inline bool operator!=(const CharacterRange& lhs, const CharacterRange& rhs) { + return !operator==(lhs, rhs); +} + +#define DECL_BOILERPLATE(Name) \ + void* Accept(RegExpVisitor* visitor, void* data) override; \ + RegExpNode* ToNode(RegExpCompiler* compiler, RegExpNode* on_success) \ + override; \ + RegExp##Name* As##Name() override; \ + bool Is##Name() override + +class RegExpTree : public ZoneObject { + public: + static const int kInfinity = kMaxInt; + virtual ~RegExpTree() = default; + virtual void* Accept(RegExpVisitor* visitor, void* data) = 0; + virtual RegExpNode* ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) = 0; + virtual bool IsTextElement() { return false; } + virtual bool IsAnchoredAtStart() { return false; } + virtual bool IsAnchoredAtEnd() { return false; } + virtual int min_match() = 0; + virtual int max_match() = 0; + // Returns the interval of registers used for captures within this + // expression. + virtual Interval CaptureRegisters() { return Interval::Empty(); } + virtual void AppendToText(RegExpText* text, Zone* zone); + V8_EXPORT_PRIVATE std::ostream& Print(std::ostream& os, Zone* zone); +#define MAKE_ASTYPE(Name) \ + virtual RegExp##Name* As##Name(); \ + virtual bool Is##Name(); + FOR_EACH_REG_EXP_TREE_TYPE(MAKE_ASTYPE) +#undef MAKE_ASTYPE +}; + + +class RegExpDisjunction final : public RegExpTree { + public: + explicit RegExpDisjunction(ZoneList<RegExpTree*>* alternatives); + + DECL_BOILERPLATE(Disjunction); + + Interval CaptureRegisters() override; + bool IsAnchoredAtStart() override; + bool IsAnchoredAtEnd() override; + int min_match() override { return min_match_; } + int max_match() override { return max_match_; } + ZoneList<RegExpTree*>* alternatives() const { return alternatives_; } + + private: + bool SortConsecutiveAtoms(RegExpCompiler* compiler); + void RationalizeConsecutiveAtoms(RegExpCompiler* compiler); + void FixSingleCharacterDisjunctions(RegExpCompiler* compiler); + ZoneList<RegExpTree*>* alternatives_; + int min_match_; + int max_match_; +}; + + +class RegExpAlternative final : public RegExpTree { + public: + explicit RegExpAlternative(ZoneList<RegExpTree*>* nodes); + + DECL_BOILERPLATE(Alternative); + + Interval CaptureRegisters() override; + bool IsAnchoredAtStart() override; + bool IsAnchoredAtEnd() override; + int min_match() override { return min_match_; } + int max_match() override { return max_match_; } + ZoneList<RegExpTree*>* nodes() const { return nodes_; } + + private: + ZoneList<RegExpTree*>* nodes_; + int min_match_; + int max_match_; +}; + + +class RegExpAssertion final : public RegExpTree { + public: + enum class Type { + START_OF_LINE = 0, + START_OF_INPUT = 1, + END_OF_LINE = 2, + END_OF_INPUT = 3, + BOUNDARY = 4, + NON_BOUNDARY = 5, + LAST_ASSERTION_TYPE = NON_BOUNDARY, + }; + explicit RegExpAssertion(Type type) : assertion_type_(type) {} + + DECL_BOILERPLATE(Assertion); + + bool IsAnchoredAtStart() override; + bool IsAnchoredAtEnd() override; + int min_match() override { return 0; } + int max_match() override { return 0; } + Type assertion_type() const { return assertion_type_; } + + private: + const Type assertion_type_; +}; + +class CharacterSet final { + public: + explicit CharacterSet(StandardCharacterSet standard_set_type) + : standard_set_type_(standard_set_type) {} + explicit CharacterSet(ZoneList<CharacterRange>* ranges) : ranges_(ranges) {} + + ZoneList<CharacterRange>* ranges(Zone* zone); + StandardCharacterSet standard_set_type() const { + return standard_set_type_.value(); + } + void set_standard_set_type(StandardCharacterSet standard_set_type) { + standard_set_type_ = standard_set_type; + } + bool is_standard() const { return standard_set_type_.has_value(); } + V8_EXPORT_PRIVATE void Canonicalize(); + + private: + ZoneList<CharacterRange>* ranges_ = nullptr; + base::Optional<StandardCharacterSet> standard_set_type_; +}; + +class RegExpClassRanges final : public RegExpTree { + 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). + enum Flag { + NEGATED = 1 << 0, + CONTAINS_SPLIT_SURROGATE = 1 << 1, + }; + using ClassRangesFlags = base::Flags<Flag>; + + RegExpClassRanges(Zone* zone, ZoneList<CharacterRange>* ranges, + ClassRangesFlags class_ranges_flags = ClassRangesFlags()) + : set_(ranges), class_ranges_flags_(class_ranges_flags) { + // Convert the empty set of ranges to the negated Everything() range. + if (ranges->is_empty()) { + ranges->Add(CharacterRange::Everything(), zone); + class_ranges_flags_ ^= NEGATED; + } + } + explicit RegExpClassRanges(StandardCharacterSet standard_set_type) + : set_(standard_set_type), class_ranges_flags_() {} + + DECL_BOILERPLATE(ClassRanges); + + bool IsTextElement() override { return true; } + int min_match() override { return 1; } + // The character class may match two code units for unicode regexps. + // TODO(yangguo): we should split this class for usage in TextElement, and + // make max_match() dependent on the character class content. + int max_match() override { return 2; } + + void AppendToText(RegExpText* text, Zone* zone) override; + + // TODO(lrn): Remove need for complex version if is_standard that + // recognizes a mangled standard set and just do { return set_.is_special(); } + bool is_standard(Zone* zone); + // Returns a value representing the standard character set if is_standard() + // returns true. + StandardCharacterSet standard_type() const { + return set_.standard_set_type(); + } + + CharacterSet character_set() const { return set_; } + ZoneList<CharacterRange>* ranges(Zone* zone) { return set_.ranges(zone); } + + bool is_negated() const { return (class_ranges_flags_ & NEGATED) != 0; } + bool contains_split_surrogate() const { + return (class_ranges_flags_ & CONTAINS_SPLIT_SURROGATE) != 0; + } + + private: + CharacterSet set_; + ClassRangesFlags class_ranges_flags_; +}; + +struct CharacterClassStringLess { + bool operator()(base::Vector<const base::uc32> lhs, + base::Vector<const base::uc32> rhs) const { + // Longer strings first so we generate matches for the largest string + // possible. + if (lhs.length() != rhs.length()) { + return lhs.length() > rhs.length(); + } + for (int i = 0; i < lhs.length(); i++) { + if (lhs[i] != rhs[i]) { + return lhs[i] < rhs[i]; + } + } + return false; + } +}; + +// A type used for strings as part of character classes (only possible in +// unicode sets mode). +// We use a ZoneMap instead of an UnorderedZoneMap because we need to match +// the longest alternatives first. By using a ZoneMap with the custom comparator +// we can avoid sorting before assembling the code. +// Strings are likely short (the largest string in current unicode properties +// consists of 10 code points). +using CharacterClassStrings = ZoneMap<base::Vector<const base::uc32>, + RegExpTree*, CharacterClassStringLess>; + +// TODO(pthier): If we are sure we don't want to use icu::UnicodeSets +// (performance evaluation pending), this class can be merged with +// RegExpClassRanges. +class RegExpClassSetOperand final : public RegExpTree { + public: + RegExpClassSetOperand(ZoneList<CharacterRange>* ranges, + CharacterClassStrings* strings); + + DECL_BOILERPLATE(ClassSetOperand); + + bool IsTextElement() override { return true; } + int min_match() override { return min_match_; } + int max_match() override { return max_match_; } + + void Union(RegExpClassSetOperand* other, Zone* zone); + void Intersect(RegExpClassSetOperand* other, + ZoneList<CharacterRange>* temp_ranges, Zone* zone); + void Subtract(RegExpClassSetOperand* other, + ZoneList<CharacterRange>* temp_ranges, Zone* zone); + + bool has_strings() const { return strings_ != nullptr && !strings_->empty(); } + ZoneList<CharacterRange>* ranges() { return ranges_; } + CharacterClassStrings* strings() { + DCHECK_NOT_NULL(strings_); + return strings_; + } + + private: + ZoneList<CharacterRange>* ranges_; + CharacterClassStrings* strings_; + int min_match_; + int max_match_; +}; + +class RegExpClassSetExpression final : public RegExpTree { + public: + enum class OperationType { kUnion, kIntersection, kSubtraction }; + + RegExpClassSetExpression(OperationType op, bool is_negated, + bool may_contain_strings, + ZoneList<RegExpTree*>* operands); + + DECL_BOILERPLATE(ClassSetExpression); + + // Create an empty class set expression (matches everything if |is_negated|, + // nothing otherwise). + static RegExpClassSetExpression* Empty(Zone* zone, bool is_negated); + + bool IsTextElement() override { return true; } + int min_match() override { return 0; } + int max_match() override { return max_match_; } + + OperationType operation() const { return operation_; } + bool is_negated() const { return is_negated_; } + bool may_contain_strings() const { return may_contain_strings_; } + const ZoneList<RegExpTree*>* operands() const { return operands_; } + ZoneList<RegExpTree*>* operands() { return operands_; } + + private: + // Recursively evaluates the tree rooted at |root|, computing the valid + // CharacterRanges and strings after applying all set operations. + // The original tree will be modified by this method, so don't store pointers + // to inner nodes of the tree somewhere else! + // Modifying the tree in-place saves memory and speeds up multiple calls of + // the method (e.g. when unrolling quantifiers). + // |temp_ranges| is used for intermediate results, passed as parameter to + // avoid allocating new lists all the time. + static RegExpClassSetOperand* ComputeExpression( + RegExpTree* root, ZoneList<CharacterRange>* temp_ranges, Zone* zone); + + const OperationType operation_; + bool is_negated_; + const bool may_contain_strings_; + ZoneList<RegExpTree*>* operands_ = nullptr; + int max_match_; +}; + +class RegExpAtom final : public RegExpTree { + public: + explicit RegExpAtom(base::Vector<const base::uc16> data) : data_(data) {} + + DECL_BOILERPLATE(Atom); + + bool IsTextElement() override { return true; } + int min_match() override { return data_.length(); } + int max_match() override { return data_.length(); } + void AppendToText(RegExpText* text, Zone* zone) override; + + base::Vector<const base::uc16> data() const { return data_; } + int length() const { return data_.length(); } + + private: + base::Vector<const base::uc16> data_; +}; + +class TextElement final { + public: + enum TextType { ATOM, CLASS_RANGES }; + + static TextElement Atom(RegExpAtom* atom); + static TextElement ClassRanges(RegExpClassRanges* class_ranges); + + int cp_offset() const { return cp_offset_; } + void set_cp_offset(int cp_offset) { cp_offset_ = cp_offset; } + int length() const; + + TextType text_type() const { return text_type_; } + + RegExpTree* tree() const { return tree_; } + + RegExpAtom* atom() const { + DCHECK(text_type() == ATOM); + return reinterpret_cast<RegExpAtom*>(tree()); + } + + RegExpClassRanges* class_ranges() const { + DCHECK(text_type() == CLASS_RANGES); + return reinterpret_cast<RegExpClassRanges*>(tree()); + } + + private: + TextElement(TextType text_type, RegExpTree* tree) + : cp_offset_(-1), text_type_(text_type), tree_(tree) {} + + int cp_offset_; + TextType text_type_; + RegExpTree* tree_; +}; + +class RegExpText final : public RegExpTree { + public: + explicit RegExpText(Zone* zone) : elements_(2, zone) {} + + DECL_BOILERPLATE(Text); + + bool IsTextElement() override { return true; } + int min_match() override { return length_; } + int max_match() override { return length_; } + void AppendToText(RegExpText* text, Zone* zone) override; + void AddElement(TextElement elm, Zone* zone) { + elements_.Add(elm, zone); + length_ += elm.length(); + } + ZoneList<TextElement>* elements() { return &elements_; } + + private: + ZoneList<TextElement> elements_; + int length_ = 0; +}; + + +class RegExpQuantifier final : public RegExpTree { + public: + enum QuantifierType { GREEDY, NON_GREEDY, POSSESSIVE }; + RegExpQuantifier(int min, int max, QuantifierType type, RegExpTree* body) + : body_(body), + min_(min), + max_(max), + quantifier_type_(type) { + if (min > 0 && body->min_match() > kInfinity / min) { + min_match_ = kInfinity; + } else { + min_match_ = min * body->min_match(); + } + if (max > 0 && body->max_match() > kInfinity / max) { + max_match_ = kInfinity; + } else { + max_match_ = max * body->max_match(); + } + } + + DECL_BOILERPLATE(Quantifier); + + static RegExpNode* ToNode(int min, int max, bool is_greedy, RegExpTree* body, + RegExpCompiler* compiler, RegExpNode* on_success, + bool not_at_start = false); + Interval CaptureRegisters() override; + int min_match() override { return min_match_; } + int max_match() override { return max_match_; } + int min() const { return min_; } + int max() const { return max_; } + QuantifierType quantifier_type() const { return quantifier_type_; } + bool is_possessive() const { return quantifier_type_ == POSSESSIVE; } + bool is_non_greedy() const { return quantifier_type_ == NON_GREEDY; } + bool is_greedy() const { return quantifier_type_ == GREEDY; } + RegExpTree* body() const { return body_; } + + private: + RegExpTree* body_; + int min_; + int max_; + int min_match_; + int max_match_; + QuantifierType quantifier_type_; +}; + + +class RegExpCapture final : public RegExpTree { + public: + explicit RegExpCapture(int index) + : body_(nullptr), + index_(index), + min_match_(0), + max_match_(0), + name_(nullptr) {} + + DECL_BOILERPLATE(Capture); + + static RegExpNode* ToNode(RegExpTree* body, int index, + RegExpCompiler* compiler, RegExpNode* on_success); + bool IsAnchoredAtStart() override; + bool IsAnchoredAtEnd() override; + Interval CaptureRegisters() override; + int min_match() override { return min_match_; } + int max_match() override { return max_match_; } + RegExpTree* body() { return body_; } + void set_body(RegExpTree* body) { + body_ = body; + min_match_ = body->min_match(); + max_match_ = body->max_match(); + } + int index() const { return index_; } + const ZoneVector<base::uc16>* name() const { return name_; } + void set_name(const ZoneVector<base::uc16>* name) { name_ = name; } + static int StartRegister(int index) { return index * 2; } + static int EndRegister(int index) { return index * 2 + 1; } + + private: + RegExpTree* body_ = nullptr; + int index_; + int min_match_ = 0; + int max_match_ = 0; + const ZoneVector<base::uc16>* name_ = nullptr; +}; + +class RegExpGroup final : public RegExpTree { + public: + explicit RegExpGroup(RegExpTree* body) + : body_(body), + min_match_(body->min_match()), + max_match_(body->max_match()) {} + + DECL_BOILERPLATE(Group); + + bool IsAnchoredAtStart() override { return body_->IsAnchoredAtStart(); } + bool IsAnchoredAtEnd() override { return body_->IsAnchoredAtEnd(); } + int min_match() override { return min_match_; } + int max_match() override { return max_match_; } + Interval CaptureRegisters() override { return body_->CaptureRegisters(); } + RegExpTree* body() const { return body_; } + + private: + RegExpTree* body_; + int min_match_; + int max_match_; +}; + +class RegExpLookaround final : public RegExpTree { + public: + enum Type { LOOKAHEAD, LOOKBEHIND }; + + RegExpLookaround(RegExpTree* body, bool is_positive, int capture_count, + int capture_from, Type type) + : body_(body), + is_positive_(is_positive), + capture_count_(capture_count), + capture_from_(capture_from), + type_(type) {} + + DECL_BOILERPLATE(Lookaround); + + Interval CaptureRegisters() override; + bool IsAnchoredAtStart() override; + int min_match() override { return 0; } + int max_match() override { return 0; } + RegExpTree* body() const { return body_; } + bool is_positive() const { return is_positive_; } + int capture_count() const { return capture_count_; } + int capture_from() const { return capture_from_; } + Type type() const { return type_; } + + class Builder { + public: + Builder(bool is_positive, RegExpNode* on_success, + int stack_pointer_register, int position_register, + int capture_register_count = 0, int capture_register_start = 0); + RegExpNode* on_match_success() const { return on_match_success_; } + RegExpNode* ForMatch(RegExpNode* match); + + private: + bool is_positive_; + RegExpNode* on_match_success_; + RegExpNode* on_success_; + int stack_pointer_register_; + int position_register_; + }; + + private: + RegExpTree* body_; + bool is_positive_; + int capture_count_; + int capture_from_; + Type type_; +}; + + +class RegExpBackReference final : public RegExpTree { + public: + explicit RegExpBackReference(RegExpFlags flags) : flags_(flags) {} + RegExpBackReference(RegExpCapture* capture, RegExpFlags flags) + : capture_(capture), flags_(flags) {} + + DECL_BOILERPLATE(BackReference); + + int min_match() override { return 0; } + // The back reference may be recursive, e.g. /(\2)(\1)/. To avoid infinite + // recursion, we give up. Ignorance is bliss. + int max_match() override { return kInfinity; } + int index() const { return capture_->index(); } + RegExpCapture* capture() const { return capture_; } + void set_capture(RegExpCapture* capture) { capture_ = capture; } + const ZoneVector<base::uc16>* name() const { return name_; } + void set_name(const ZoneVector<base::uc16>* name) { name_ = name; } + + private: + RegExpCapture* capture_ = nullptr; + const ZoneVector<base::uc16>* name_ = nullptr; + const RegExpFlags flags_; +}; + + +class RegExpEmpty final : public RegExpTree { + public: + DECL_BOILERPLATE(Empty); + int min_match() override { return 0; } + int max_match() override { return 0; } +}; + +} // namespace internal +} // namespace v8 + +#undef DECL_BOILERPLATE + +#endif // V8_REGEXP_REGEXP_AST_H_ diff --git a/js/src/irregexp/imported/regexp-bytecode-generator-inl.h b/js/src/irregexp/imported/regexp-bytecode-generator-inl.h new file mode 100644 index 0000000000..807ca66f47 --- /dev/null +++ b/js/src/irregexp/imported/regexp-bytecode-generator-inl.h @@ -0,0 +1,55 @@ +// Copyright 2008-2009 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. + +#ifndef V8_REGEXP_REGEXP_BYTECODE_GENERATOR_INL_H_ +#define V8_REGEXP_REGEXP_BYTECODE_GENERATOR_INL_H_ + +#include "irregexp/imported/regexp-bytecode-generator.h" + +#include "irregexp/imported/regexp-bytecodes.h" + +namespace v8 { +namespace internal { + +void RegExpBytecodeGenerator::Emit(uint32_t byte, uint32_t twenty_four_bits) { + DCHECK(is_uint24(twenty_four_bits)); + Emit32((twenty_four_bits << BYTECODE_SHIFT) | byte); +} + +void RegExpBytecodeGenerator::Emit(uint32_t byte, int32_t twenty_four_bits) { + DCHECK(is_int24(twenty_four_bits)); + Emit32((static_cast<uint32_t>(twenty_four_bits) << BYTECODE_SHIFT) | byte); +} + +void RegExpBytecodeGenerator::Emit16(uint32_t word) { + DCHECK(pc_ <= static_cast<int>(buffer_.size())); + if (pc_ + 1 >= static_cast<int>(buffer_.size())) { + ExpandBuffer(); + } + *reinterpret_cast<uint16_t*>(buffer_.data() + pc_) = word; + pc_ += 2; +} + +void RegExpBytecodeGenerator::Emit8(uint32_t word) { + DCHECK(pc_ <= static_cast<int>(buffer_.size())); + if (pc_ == static_cast<int>(buffer_.size())) { + ExpandBuffer(); + } + *reinterpret_cast<unsigned char*>(buffer_.data() + pc_) = word; + pc_ += 1; +} + +void RegExpBytecodeGenerator::Emit32(uint32_t word) { + DCHECK(pc_ <= static_cast<int>(buffer_.size())); + if (pc_ + 3 >= static_cast<int>(buffer_.size())) { + ExpandBuffer(); + } + *reinterpret_cast<uint32_t*>(buffer_.data() + pc_) = word; + pc_ += 4; +} + +} // namespace internal +} // namespace v8 + +#endif // V8_REGEXP_REGEXP_BYTECODE_GENERATOR_INL_H_ diff --git a/js/src/irregexp/imported/regexp-bytecode-generator.cc b/js/src/irregexp/imported/regexp-bytecode-generator.cc new file mode 100644 index 0000000000..c83e10a598 --- /dev/null +++ b/js/src/irregexp/imported/regexp-bytecode-generator.cc @@ -0,0 +1,405 @@ +// Copyright 2008-2009 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-bytecode-generator.h" + +#include "irregexp/imported/regexp-bytecode-generator-inl.h" +#include "irregexp/imported/regexp-bytecode-peephole.h" +#include "irregexp/imported/regexp-bytecodes.h" +#include "irregexp/imported/regexp-macro-assembler.h" + +namespace v8 { +namespace internal { + +RegExpBytecodeGenerator::RegExpBytecodeGenerator(Isolate* isolate, Zone* zone) + : RegExpMacroAssembler(isolate, zone), + buffer_(kInitialBufferSize, zone), + pc_(0), + advance_current_end_(kInvalidPC), + jump_edges_(zone), + isolate_(isolate) {} + +RegExpBytecodeGenerator::~RegExpBytecodeGenerator() { + if (backtrack_.is_linked()) backtrack_.Unuse(); +} + +RegExpBytecodeGenerator::IrregexpImplementation +RegExpBytecodeGenerator::Implementation() { + return kBytecodeImplementation; +} + +void RegExpBytecodeGenerator::Bind(Label* l) { + advance_current_end_ = kInvalidPC; + DCHECK(!l->is_bound()); + if (l->is_linked()) { + int pos = l->pos(); + while (pos != 0) { + int fixup = pos; + pos = *reinterpret_cast<int32_t*>(buffer_.data() + fixup); + *reinterpret_cast<uint32_t*>(buffer_.data() + fixup) = pc_; + jump_edges_.emplace(fixup, pc_); + } + } + l->bind_to(pc_); +} + +void RegExpBytecodeGenerator::EmitOrLink(Label* l) { + if (l == nullptr) l = &backtrack_; + int pos = 0; + if (l->is_bound()) { + pos = l->pos(); + jump_edges_.emplace(pc_, pos); + } else { + if (l->is_linked()) { + pos = l->pos(); + } + l->link_to(pc_); + } + Emit32(pos); +} + +void RegExpBytecodeGenerator::PopRegister(int register_index) { + DCHECK_LE(0, register_index); + DCHECK_GE(kMaxRegister, register_index); + Emit(BC_POP_REGISTER, register_index); +} + +void RegExpBytecodeGenerator::PushRegister(int register_index, + StackCheckFlag check_stack_limit) { + DCHECK_LE(0, register_index); + DCHECK_GE(kMaxRegister, register_index); + Emit(BC_PUSH_REGISTER, register_index); +} + +void RegExpBytecodeGenerator::WriteCurrentPositionToRegister(int register_index, + int cp_offset) { + DCHECK_LE(0, register_index); + DCHECK_GE(kMaxRegister, register_index); + Emit(BC_SET_REGISTER_TO_CP, register_index); + Emit32(cp_offset); // Current position offset. +} + +void RegExpBytecodeGenerator::ClearRegisters(int reg_from, int reg_to) { + DCHECK(reg_from <= reg_to); + for (int reg = reg_from; reg <= reg_to; reg++) { + SetRegister(reg, -1); + } +} + +void RegExpBytecodeGenerator::ReadCurrentPositionFromRegister( + int register_index) { + DCHECK_LE(0, register_index); + DCHECK_GE(kMaxRegister, register_index); + Emit(BC_SET_CP_TO_REGISTER, register_index); +} + +void RegExpBytecodeGenerator::WriteStackPointerToRegister(int register_index) { + DCHECK_LE(0, register_index); + DCHECK_GE(kMaxRegister, register_index); + Emit(BC_SET_REGISTER_TO_SP, register_index); +} + +void RegExpBytecodeGenerator::ReadStackPointerFromRegister(int register_index) { + DCHECK_LE(0, register_index); + DCHECK_GE(kMaxRegister, register_index); + Emit(BC_SET_SP_TO_REGISTER, register_index); +} + +void RegExpBytecodeGenerator::SetCurrentPositionFromEnd(int by) { + DCHECK(is_uint24(by)); + Emit(BC_SET_CURRENT_POSITION_FROM_END, by); +} + +void RegExpBytecodeGenerator::SetRegister(int register_index, int to) { + DCHECK_LE(0, register_index); + DCHECK_GE(kMaxRegister, register_index); + Emit(BC_SET_REGISTER, register_index); + Emit32(to); +} + +void RegExpBytecodeGenerator::AdvanceRegister(int register_index, int by) { + DCHECK_LE(0, register_index); + DCHECK_GE(kMaxRegister, register_index); + Emit(BC_ADVANCE_REGISTER, register_index); + Emit32(by); +} + +void RegExpBytecodeGenerator::PopCurrentPosition() { Emit(BC_POP_CP, 0); } + +void RegExpBytecodeGenerator::PushCurrentPosition() { Emit(BC_PUSH_CP, 0); } + +void RegExpBytecodeGenerator::Backtrack() { + int error_code = + can_fallback() ? RegExp::RE_FALLBACK_TO_EXPERIMENTAL : RegExp::RE_FAILURE; + Emit(BC_POP_BT, error_code); +} + +void RegExpBytecodeGenerator::GoTo(Label* l) { + if (advance_current_end_ == pc_) { + // Combine advance current and goto. + pc_ = advance_current_start_; + Emit(BC_ADVANCE_CP_AND_GOTO, advance_current_offset_); + EmitOrLink(l); + advance_current_end_ = kInvalidPC; + } else { + // Regular goto. + Emit(BC_GOTO, 0); + EmitOrLink(l); + } +} + +void RegExpBytecodeGenerator::PushBacktrack(Label* l) { + Emit(BC_PUSH_BT, 0); + EmitOrLink(l); +} + +bool RegExpBytecodeGenerator::Succeed() { + Emit(BC_SUCCEED, 0); + return false; // Restart matching for global regexp not supported. +} + +void RegExpBytecodeGenerator::Fail() { Emit(BC_FAIL, 0); } + +void RegExpBytecodeGenerator::AdvanceCurrentPosition(int by) { + // TODO(chromium:1166138): Turn back into DCHECKs once the underlying issue + // is fixed. + CHECK_LE(kMinCPOffset, by); + CHECK_GE(kMaxCPOffset, by); + advance_current_start_ = pc_; + advance_current_offset_ = by; + Emit(BC_ADVANCE_CP, by); + advance_current_end_ = pc_; +} + +void RegExpBytecodeGenerator::CheckGreedyLoop( + Label* on_tos_equals_current_position) { + Emit(BC_CHECK_GREEDY, 0); + EmitOrLink(on_tos_equals_current_position); +} + +void RegExpBytecodeGenerator::LoadCurrentCharacterImpl(int cp_offset, + Label* on_failure, + bool check_bounds, + int characters, + int eats_at_least) { + DCHECK_GE(eats_at_least, characters); + if (eats_at_least > characters && check_bounds) { + DCHECK(is_int24(cp_offset + eats_at_least)); + Emit(BC_CHECK_CURRENT_POSITION, cp_offset + eats_at_least); + EmitOrLink(on_failure); + check_bounds = false; // Load below doesn't need to check. + } + + DCHECK_LE(kMinCPOffset, cp_offset); + DCHECK_GE(kMaxCPOffset, cp_offset); + int bytecode; + if (check_bounds) { + if (characters == 4) { + bytecode = BC_LOAD_4_CURRENT_CHARS; + } else if (characters == 2) { + bytecode = BC_LOAD_2_CURRENT_CHARS; + } else { + DCHECK_EQ(1, characters); + bytecode = BC_LOAD_CURRENT_CHAR; + } + } else { + if (characters == 4) { + bytecode = BC_LOAD_4_CURRENT_CHARS_UNCHECKED; + } else if (characters == 2) { + bytecode = BC_LOAD_2_CURRENT_CHARS_UNCHECKED; + } else { + DCHECK_EQ(1, characters); + bytecode = BC_LOAD_CURRENT_CHAR_UNCHECKED; + } + } + Emit(bytecode, cp_offset); + if (check_bounds) EmitOrLink(on_failure); +} + +void RegExpBytecodeGenerator::CheckCharacterLT(base::uc16 limit, + Label* on_less) { + Emit(BC_CHECK_LT, limit); + EmitOrLink(on_less); +} + +void RegExpBytecodeGenerator::CheckCharacterGT(base::uc16 limit, + Label* on_greater) { + Emit(BC_CHECK_GT, limit); + EmitOrLink(on_greater); +} + +void RegExpBytecodeGenerator::CheckCharacter(uint32_t c, Label* on_equal) { + if (c > MAX_FIRST_ARG) { + Emit(BC_CHECK_4_CHARS, 0); + Emit32(c); + } else { + Emit(BC_CHECK_CHAR, c); + } + EmitOrLink(on_equal); +} + +void RegExpBytecodeGenerator::CheckAtStart(int cp_offset, Label* on_at_start) { + Emit(BC_CHECK_AT_START, cp_offset); + EmitOrLink(on_at_start); +} + +void RegExpBytecodeGenerator::CheckNotAtStart(int cp_offset, + Label* on_not_at_start) { + Emit(BC_CHECK_NOT_AT_START, cp_offset); + EmitOrLink(on_not_at_start); +} + +void RegExpBytecodeGenerator::CheckNotCharacter(uint32_t c, + Label* on_not_equal) { + if (c > MAX_FIRST_ARG) { + Emit(BC_CHECK_NOT_4_CHARS, 0); + Emit32(c); + } else { + Emit(BC_CHECK_NOT_CHAR, c); + } + EmitOrLink(on_not_equal); +} + +void RegExpBytecodeGenerator::CheckCharacterAfterAnd(uint32_t c, uint32_t mask, + Label* on_equal) { + if (c > MAX_FIRST_ARG) { + Emit(BC_AND_CHECK_4_CHARS, 0); + Emit32(c); + } else { + Emit(BC_AND_CHECK_CHAR, c); + } + Emit32(mask); + EmitOrLink(on_equal); +} + +void RegExpBytecodeGenerator::CheckNotCharacterAfterAnd(uint32_t c, + uint32_t mask, + Label* on_not_equal) { + if (c > MAX_FIRST_ARG) { + Emit(BC_AND_CHECK_NOT_4_CHARS, 0); + Emit32(c); + } else { + Emit(BC_AND_CHECK_NOT_CHAR, c); + } + Emit32(mask); + EmitOrLink(on_not_equal); +} + +void RegExpBytecodeGenerator::CheckNotCharacterAfterMinusAnd( + base::uc16 c, base::uc16 minus, base::uc16 mask, Label* on_not_equal) { + Emit(BC_MINUS_AND_CHECK_NOT_CHAR, c); + Emit16(minus); + Emit16(mask); + EmitOrLink(on_not_equal); +} + +void RegExpBytecodeGenerator::CheckCharacterInRange(base::uc16 from, + base::uc16 to, + Label* on_in_range) { + Emit(BC_CHECK_CHAR_IN_RANGE, 0); + Emit16(from); + Emit16(to); + EmitOrLink(on_in_range); +} + +void RegExpBytecodeGenerator::CheckCharacterNotInRange(base::uc16 from, + base::uc16 to, + Label* on_not_in_range) { + Emit(BC_CHECK_CHAR_NOT_IN_RANGE, 0); + Emit16(from); + Emit16(to); + EmitOrLink(on_not_in_range); +} + +void RegExpBytecodeGenerator::CheckBitInTable(Handle<ByteArray> table, + Label* on_bit_set) { + Emit(BC_CHECK_BIT_IN_TABLE, 0); + EmitOrLink(on_bit_set); + for (int i = 0; i < kTableSize; i += kBitsPerByte) { + int byte = 0; + for (int j = 0; j < kBitsPerByte; j++) { + if (table->get(i + j) != 0) byte |= 1 << j; + } + Emit8(byte); + } +} + +void RegExpBytecodeGenerator::CheckNotBackReference(int start_reg, + bool read_backward, + Label* on_not_equal) { + DCHECK_LE(0, start_reg); + DCHECK_GE(kMaxRegister, start_reg); + Emit(read_backward ? BC_CHECK_NOT_BACK_REF_BACKWARD : BC_CHECK_NOT_BACK_REF, + start_reg); + EmitOrLink(on_not_equal); +} + +void RegExpBytecodeGenerator::CheckNotBackReferenceIgnoreCase( + int start_reg, bool read_backward, bool unicode, Label* on_not_equal) { + DCHECK_LE(0, start_reg); + DCHECK_GE(kMaxRegister, start_reg); + Emit(read_backward ? (unicode ? BC_CHECK_NOT_BACK_REF_NO_CASE_UNICODE_BACKWARD + : BC_CHECK_NOT_BACK_REF_NO_CASE_BACKWARD) + : (unicode ? BC_CHECK_NOT_BACK_REF_NO_CASE_UNICODE + : BC_CHECK_NOT_BACK_REF_NO_CASE), + start_reg); + EmitOrLink(on_not_equal); +} + +void RegExpBytecodeGenerator::IfRegisterLT(int register_index, int comparand, + Label* on_less_than) { + DCHECK_LE(0, register_index); + DCHECK_GE(kMaxRegister, register_index); + Emit(BC_CHECK_REGISTER_LT, register_index); + Emit32(comparand); + EmitOrLink(on_less_than); +} + +void RegExpBytecodeGenerator::IfRegisterGE(int register_index, int comparand, + Label* on_greater_or_equal) { + DCHECK_LE(0, register_index); + DCHECK_GE(kMaxRegister, register_index); + Emit(BC_CHECK_REGISTER_GE, register_index); + Emit32(comparand); + EmitOrLink(on_greater_or_equal); +} + +void RegExpBytecodeGenerator::IfRegisterEqPos(int register_index, + Label* on_eq) { + DCHECK_LE(0, register_index); + DCHECK_GE(kMaxRegister, register_index); + Emit(BC_CHECK_REGISTER_EQ_POS, register_index); + EmitOrLink(on_eq); +} + +Handle<HeapObject> RegExpBytecodeGenerator::GetCode(Handle<String> source) { + Bind(&backtrack_); + Backtrack(); + + Handle<ByteArray> array; + 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()); + } + + return array; +} + +int RegExpBytecodeGenerator::length() { return pc_; } + +void RegExpBytecodeGenerator::Copy(uint8_t* a) { + MemCopy(a, buffer_.data(), length()); +} + +void RegExpBytecodeGenerator::ExpandBuffer() { + // TODO(jgruber): The growth strategy could be smarter for large sizes. + // TODO(jgruber): It's not necessary to default-initialize new elements. + buffer_.resize(buffer_.size() * 2); +} + +} // namespace internal +} // namespace v8 diff --git a/js/src/irregexp/imported/regexp-bytecode-generator.h b/js/src/irregexp/imported/regexp-bytecode-generator.h new file mode 100644 index 0000000000..18ac22c808 --- /dev/null +++ b/js/src/irregexp/imported/regexp-bytecode-generator.h @@ -0,0 +1,140 @@ +// Copyright 2012 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. + +#ifndef V8_REGEXP_REGEXP_BYTECODE_GENERATOR_H_ +#define V8_REGEXP_REGEXP_BYTECODE_GENERATOR_H_ + +#include "irregexp/imported/regexp-macro-assembler.h" + +namespace v8 { +namespace internal { + +// An assembler/generator for the Irregexp byte code. +class V8_EXPORT_PRIVATE RegExpBytecodeGenerator : public RegExpMacroAssembler { + public: + // Create an assembler. Instructions and relocation information are emitted + // into a buffer, with the instructions starting from the beginning and the + // relocation information starting from the end of the buffer. See CodeDesc + // for a detailed comment on the layout (globals.h). + // + // The assembler allocates and grows its own buffer, and buffer_size + // determines the initial buffer size. The buffer is owned by the assembler + // and deallocated upon destruction of the assembler. + RegExpBytecodeGenerator(Isolate* isolate, Zone* zone); + ~RegExpBytecodeGenerator() override; + // The byte-code interpreter checks on each push anyway. + int stack_limit_slack() override { return 1; } + bool CanReadUnaligned() const override { return false; } + void Bind(Label* label) override; + void AdvanceCurrentPosition(int by) override; // Signed cp change. + void PopCurrentPosition() override; + void PushCurrentPosition() override; + void Backtrack() override; + void GoTo(Label* label) override; + void PushBacktrack(Label* label) override; + bool Succeed() override; + void Fail() override; + void PopRegister(int register_index) override; + void PushRegister(int register_index, + StackCheckFlag check_stack_limit) override; + void AdvanceRegister(int reg, int by) override; // r[reg] += by. + void SetCurrentPositionFromEnd(int by) override; + void SetRegister(int register_index, int to) override; + void WriteCurrentPositionToRegister(int reg, int cp_offset) override; + void ClearRegisters(int reg_from, int reg_to) override; + void ReadCurrentPositionFromRegister(int reg) override; + void WriteStackPointerToRegister(int reg) override; + void ReadStackPointerFromRegister(int reg) override; + void LoadCurrentCharacterImpl(int cp_offset, Label* on_end_of_input, + bool check_bounds, int characters, + int eats_at_least) override; + void CheckCharacter(unsigned c, Label* on_equal) override; + void CheckCharacterAfterAnd(unsigned c, unsigned mask, + Label* on_equal) override; + void CheckCharacterGT(base::uc16 limit, Label* on_greater) override; + void CheckCharacterLT(base::uc16 limit, Label* on_less) override; + void CheckGreedyLoop(Label* on_tos_equals_current_position) override; + void CheckAtStart(int cp_offset, Label* on_at_start) override; + void CheckNotAtStart(int cp_offset, Label* on_not_at_start) override; + void CheckNotCharacter(unsigned c, Label* on_not_equal) override; + void CheckNotCharacterAfterAnd(unsigned c, unsigned mask, + Label* on_not_equal) override; + void CheckNotCharacterAfterMinusAnd(base::uc16 c, base::uc16 minus, + base::uc16 mask, + Label* on_not_equal) override; + void CheckCharacterInRange(base::uc16 from, base::uc16 to, + Label* on_in_range) override; + void CheckCharacterNotInRange(base::uc16 from, base::uc16 to, + Label* on_not_in_range) override; + bool CheckCharacterInRangeArray(const ZoneList<CharacterRange>* ranges, + Label* on_in_range) override { + // Disabled in the interpreter, because 1) there is no constant pool that + // could store the ByteArray pointer, 2) bytecode size limits are not as + // restrictive as code (e.g. branch distances on arm), 3) bytecode for + // large character classes is already quite compact. + // TODO(jgruber): Consider using BytecodeArrays (with a constant pool) + // instead of plain ByteArrays; then we could implement + // CheckCharacterInRangeArray in the interpreter. + return false; + } + bool CheckCharacterNotInRangeArray(const ZoneList<CharacterRange>* ranges, + Label* on_not_in_range) override { + return false; + } + void CheckBitInTable(Handle<ByteArray> table, Label* on_bit_set) override; + void CheckNotBackReference(int start_reg, bool read_backward, + Label* on_no_match) override; + void CheckNotBackReferenceIgnoreCase(int start_reg, bool read_backward, + bool unicode, + Label* on_no_match) override; + void IfRegisterLT(int register_index, int comparand, Label* if_lt) override; + void IfRegisterGE(int register_index, int comparand, Label* if_ge) override; + void IfRegisterEqPos(int register_index, Label* if_eq) override; + + IrregexpImplementation Implementation() override; + Handle<HeapObject> GetCode(Handle<String> source) override; + + private: + void ExpandBuffer(); + + // Code and bitmap emission. + inline void EmitOrLink(Label* label); + inline void Emit32(uint32_t x); + inline void Emit16(uint32_t x); + inline void Emit8(uint32_t x); + inline void Emit(uint32_t bc, uint32_t arg); + inline void Emit(uint32_t bc, int32_t arg); + // Bytecode buffer. + int length(); + void Copy(uint8_t* a); + + // The buffer into which code and relocation info are generated. + static constexpr int kInitialBufferSize = 1024; + ZoneVector<uint8_t> buffer_; + + // The program counter. + int pc_; + Label backtrack_; + + int advance_current_start_; + int advance_current_offset_; + int advance_current_end_; + + // Stores jump edges emitted for the bytecode (used by + // RegExpBytecodePeepholeOptimization). + // Key: jump source (offset in buffer_ where jump destination is stored). + // Value: jump destination (offset in buffer_ to jump to). + ZoneUnorderedMap<int, int> jump_edges_; + + Isolate* isolate_; + + static const int kInvalidPC = -1; + + DISALLOW_IMPLICIT_CONSTRUCTORS(RegExpBytecodeGenerator); +}; + +} // namespace internal +} // namespace v8 + +#endif // V8_REGEXP_REGEXP_BYTECODE_GENERATOR_H_ diff --git a/js/src/irregexp/imported/regexp-bytecode-peephole.cc b/js/src/irregexp/imported/regexp-bytecode-peephole.cc new file mode 100644 index 0000000000..ec8dcf1108 --- /dev/null +++ b/js/src/irregexp/imported/regexp-bytecode-peephole.cc @@ -0,0 +1,1029 @@ +// 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-bytecode-peephole.h" + +#include "irregexp/imported/regexp-bytecodes.h" + +namespace v8 { +namespace internal { + +namespace { + +struct BytecodeArgument { + int offset; + int length; + + BytecodeArgument(int offset, int length) : offset(offset), length(length) {} +}; + +struct BytecodeArgumentMapping : BytecodeArgument { + int new_length; + + BytecodeArgumentMapping(int offset, int length, int new_length) + : BytecodeArgument(offset, length), new_length(new_length) {} +}; + +struct BytecodeArgumentCheck : BytecodeArgument { + enum CheckType { kCheckAddress = 0, kCheckValue }; + CheckType type; + int check_offset; + int check_length; + + BytecodeArgumentCheck(int offset, int length, int check_offset) + : BytecodeArgument(offset, length), + type(kCheckAddress), + check_offset(check_offset) {} + BytecodeArgumentCheck(int offset, int length, int check_offset, + int check_length) + : BytecodeArgument(offset, length), + type(kCheckValue), + check_offset(check_offset), + check_length(check_length) {} +}; + +// Trie-Node for storing bytecode sequences we want to optimize. +class BytecodeSequenceNode { + public: + // Dummy bytecode used when we need to store/return a bytecode but it's not a + // valid bytecode in the current context. + static constexpr int kDummyBytecode = -1; + + BytecodeSequenceNode(int bytecode, Zone* zone); + // Adds a new node as child of the current node if it isn't a child already. + BytecodeSequenceNode& FollowedBy(int bytecode); + // Marks the end of a sequence and sets optimized bytecode to replace all + // bytecodes of the sequence with. + BytecodeSequenceNode& ReplaceWith(int bytecode); + // Maps arguments of bytecodes in the sequence to the optimized bytecode. + // Order of invocation determines order of arguments in the optimized + // bytecode. + // Invoking this method is only allowed on nodes that mark the end of a valid + // sequence (i.e. after ReplaceWith()). + // bytecode_index_in_sequence: Zero-based index of the referred bytecode + // within the sequence (e.g. the bytecode passed to CreateSequence() has + // index 0). + // argument_offset: Zero-based offset to the argument within the bytecode + // (e.g. the first argument that's not packed with the bytecode has offset 4). + // argument_byte_length: Length of the argument. + // new_argument_byte_length: Length of the argument in the new bytecode + // (= argument_byte_length if omitted). + BytecodeSequenceNode& MapArgument(int bytecode_index_in_sequence, + int argument_offset, + int argument_byte_length, + int new_argument_byte_length = 0); + // Adds a check to the sequence node making it only a valid sequence when the + // argument of the current bytecode at the specified offset matches the offset + // to check against. + // argument_offset: Zero-based offset to the argument within the bytecode + // (e.g. the first argument that's not packed with the bytecode has offset 4). + // argument_byte_length: Length of the argument. + // check_byte_offset: Zero-based offset relative to the beginning of the + // sequence that needs to match the value given by argument_offset. (e.g. + // check_byte_offset 0 matches the address of the first bytecode in the + // sequence). + BytecodeSequenceNode& IfArgumentEqualsOffset(int argument_offset, + int argument_byte_length, + int check_byte_offset); + // Adds a check to the sequence node making it only a valid sequence when the + // argument of the current bytecode at the specified offset matches the + // argument of another bytecode in the sequence. + // This is similar to IfArgumentEqualsOffset, except that this method matches + // the values of both arguments. + BytecodeSequenceNode& IfArgumentEqualsValueAtOffset( + int argument_offset, int argument_byte_length, + int other_bytecode_index_in_sequence, int other_argument_offset, + int other_argument_byte_length); + // Marks an argument as unused. + // All arguments that are not mapped explicitly have to be marked as unused. + // bytecode_index_in_sequence: Zero-based index of the referred bytecode + // within the sequence (e.g. the bytecode passed to CreateSequence() has + // index 0). + // argument_offset: Zero-based offset to the argument within the bytecode + // (e.g. the first argument that's not packed with the bytecode has offset 4). + // argument_byte_length: Length of the argument. + BytecodeSequenceNode& IgnoreArgument(int bytecode_index_in_sequence, + int argument_offset, + int argument_byte_length); + // Checks if the current node is valid for the sequence. I.e. all conditions + // set by IfArgumentEqualsOffset and IfArgumentEquals are fulfilled by this + // node for the actual bytecode sequence. + bool CheckArguments(const uint8_t* bytecode, int pc); + // Returns whether this node marks the end of a valid sequence (i.e. can be + // replaced with an optimized bytecode). + bool IsSequence() const; + // Returns the length of the sequence in bytes. + int SequenceLength() const; + // Returns the optimized bytecode for the node or kDummyBytecode if it is not + // the end of a valid sequence. + int OptimizedBytecode() const; + // Returns the child of the current node matching the given bytecode or + // nullptr if no such child is found. + BytecodeSequenceNode* Find(int bytecode) const; + // Returns number of arguments mapped to the current node. + // Invoking this method is only allowed on nodes that mark the end of a valid + // sequence (i.e. if IsSequence()) + size_t ArgumentSize() const; + // Returns the argument-mapping of the argument at index. + // Invoking this method is only allowed on nodes that mark the end of a valid + // sequence (i.e. if IsSequence()) + BytecodeArgumentMapping ArgumentMapping(size_t index) const; + // Returns an iterator to begin of ignored arguments. + // Invoking this method is only allowed on nodes that mark the end of a valid + // sequence (i.e. if IsSequence()) + ZoneLinkedList<BytecodeArgument>::iterator ArgumentIgnoredBegin() const; + // Returns an iterator to end of ignored arguments. + // Invoking this method is only allowed on nodes that mark the end of a valid + // sequence (i.e. if IsSequence()) + ZoneLinkedList<BytecodeArgument>::iterator ArgumentIgnoredEnd() const; + // Returns whether the current node has ignored argument or not. + bool HasIgnoredArguments() const; + + private: + // Returns a node in the sequence specified by its index within the sequence. + BytecodeSequenceNode& GetNodeByIndexInSequence(int index_in_sequence); + Zone* zone() const; + + int bytecode_; + int bytecode_replacement_; + int index_in_sequence_; + int start_offset_; + BytecodeSequenceNode* parent_; + ZoneUnorderedMap<int, BytecodeSequenceNode*> children_; + ZoneVector<BytecodeArgumentMapping>* argument_mapping_; + ZoneLinkedList<BytecodeArgumentCheck>* argument_check_; + ZoneLinkedList<BytecodeArgument>* argument_ignored_; + + Zone* zone_; +}; + +// These definitions are here in order to please the linker, which in debug mode +// sometimes requires static constants to be defined in .cc files. +constexpr int BytecodeSequenceNode::kDummyBytecode; + +class RegExpBytecodePeephole { + public: + RegExpBytecodePeephole(Zone* zone, size_t buffer_size, + const ZoneUnorderedMap<int, int>& jump_edges); + + // Parses bytecode and fills the internal buffer with the potentially + // optimized bytecode. Returns true when optimizations were performed, false + // otherwise. + bool OptimizeBytecode(const uint8_t* bytecode, int length); + // Copies the internal bytecode buffer to another buffer. The caller is + // responsible for allocating/freeing the memory. + void CopyOptimizedBytecode(uint8_t* to_address) const; + int Length() const; + + private: + // Sets up all sequences that are going to be used. + void DefineStandardSequences(); + // Starts a new bytecode sequence. + BytecodeSequenceNode& CreateSequence(int bytecode); + // Checks for optimization candidates at pc and emits optimized bytecode to + // the internal buffer. Returns the length of replaced bytecodes in bytes. + int TryOptimizeSequence(const uint8_t* bytecode, int bytecode_length, + int start_pc); + // Emits optimized bytecode to the internal buffer. start_pc points to the + // start of the sequence in bytecode and last_node is the last + // BytecodeSequenceNode of the matching sequence found. + void EmitOptimization(int start_pc, const uint8_t* bytecode, + const BytecodeSequenceNode& last_node); + // Adds a relative jump source fixup at pos. + // Jump source fixups are used to find offsets in the new bytecode that + // contain jump sources. + void AddJumpSourceFixup(int fixup, int pos); + // Adds a relative jump destination fixup at pos. + // Jump destination fixups are used to find offsets in the new bytecode that + // can be jumped to. + void AddJumpDestinationFixup(int fixup, int pos); + // Sets an absolute jump destination fixup at pos. + void SetJumpDestinationFixup(int fixup, int pos); + // Prepare internal structures used to fixup jumps. + void PrepareJumpStructures(const ZoneUnorderedMap<int, int>& jump_edges); + // Updates all jump targets in the new bytecode. + void FixJumps(); + // Update a single jump. + void FixJump(int jump_source, int jump_destination); + void AddSentinelFixups(int pos); + template <typename T> + void EmitValue(T value); + template <typename T> + void OverwriteValue(int offset, T value); + void CopyRangeToOutput(const uint8_t* orig_bytecode, int start, int length); + void SetRange(uint8_t value, int count); + void EmitArgument(int start_pc, const uint8_t* bytecode, + BytecodeArgumentMapping arg); + int pc() const; + Zone* zone() const; + + ZoneVector<uint8_t> optimized_bytecode_buffer_; + BytecodeSequenceNode* sequences_; + // Jumps used in old bytecode. + // Key: Jump source (offset where destination is stored in old bytecode) + // Value: Destination + ZoneMap<int, int> jump_edges_; + // Jumps used in new bytecode. + // Key: Jump source (offset where destination is stored in new bytecode) + // Value: Destination + ZoneMap<int, int> jump_edges_mapped_; + // Number of times a jump destination is used within the bytecode. + // Key: Jump destination (offset in old bytecode). + // Value: Number of times jump destination is used. + ZoneMap<int, int> jump_usage_counts_; + // Maps offsets in old bytecode to fixups of sources (delta to new bytecode). + // Key: Offset in old bytecode from where the fixup is valid. + // Value: Delta to map jump source from old bytecode to new bytecode in bytes. + ZoneMap<int, int> jump_source_fixups_; + // Maps offsets in old bytecode to fixups of destinations (delta to new + // bytecode). + // Key: Offset in old bytecode from where the fixup is valid. + // Value: Delta to map jump destinations from old bytecode to new bytecode in + // bytes. + ZoneMap<int, int> jump_destination_fixups_; + + Zone* zone_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(RegExpBytecodePeephole); +}; + +template <typename T> +T GetValue(const uint8_t* buffer, int pos) { + DCHECK(IsAligned(reinterpret_cast<Address>(buffer + pos), alignof(T))); + return *reinterpret_cast<const T*>(buffer + pos); +} + +int32_t GetArgumentValue(const uint8_t* bytecode, int offset, int length) { + switch (length) { + case 1: + return GetValue<uint8_t>(bytecode, offset); + case 2: + return GetValue<int16_t>(bytecode, offset); + case 4: + return GetValue<int32_t>(bytecode, offset); + default: + UNREACHABLE(); + } +} + +BytecodeSequenceNode::BytecodeSequenceNode(int bytecode, Zone* zone) + : bytecode_(bytecode), + bytecode_replacement_(kDummyBytecode), + index_in_sequence_(0), + start_offset_(0), + parent_(nullptr), + children_(ZoneUnorderedMap<int, BytecodeSequenceNode*>(zone)), + argument_mapping_(zone->New<ZoneVector<BytecodeArgumentMapping>>(zone)), + argument_check_(zone->New<ZoneLinkedList<BytecodeArgumentCheck>>(zone)), + argument_ignored_(zone->New<ZoneLinkedList<BytecodeArgument>>(zone)), + zone_(zone) {} + +BytecodeSequenceNode& BytecodeSequenceNode::FollowedBy(int bytecode) { + DCHECK(0 <= bytecode && bytecode < kRegExpBytecodeCount); + + if (children_.find(bytecode) == children_.end()) { + BytecodeSequenceNode* new_node = + zone()->New<BytecodeSequenceNode>(bytecode, zone()); + // If node is not the first in the sequence, set offsets and parent. + if (bytecode_ != kDummyBytecode) { + new_node->start_offset_ = start_offset_ + RegExpBytecodeLength(bytecode_); + new_node->index_in_sequence_ = index_in_sequence_ + 1; + new_node->parent_ = this; + } + children_[bytecode] = new_node; + } + + return *children_[bytecode]; +} + +BytecodeSequenceNode& BytecodeSequenceNode::ReplaceWith(int bytecode) { + DCHECK(0 <= bytecode && bytecode < kRegExpBytecodeCount); + + bytecode_replacement_ = bytecode; + + return *this; +} + +BytecodeSequenceNode& BytecodeSequenceNode::MapArgument( + int bytecode_index_in_sequence, int argument_offset, + int argument_byte_length, int new_argument_byte_length) { + DCHECK(IsSequence()); + DCHECK_LE(bytecode_index_in_sequence, index_in_sequence_); + + BytecodeSequenceNode& ref_node = + GetNodeByIndexInSequence(bytecode_index_in_sequence); + DCHECK_LT(argument_offset, RegExpBytecodeLength(ref_node.bytecode_)); + + int absolute_offset = ref_node.start_offset_ + argument_offset; + if (new_argument_byte_length == 0) { + new_argument_byte_length = argument_byte_length; + } + + argument_mapping_->push_back(BytecodeArgumentMapping{ + absolute_offset, argument_byte_length, new_argument_byte_length}); + + return *this; +} + +BytecodeSequenceNode& BytecodeSequenceNode::IfArgumentEqualsOffset( + int argument_offset, int argument_byte_length, int check_byte_offset) { + DCHECK_LT(argument_offset, RegExpBytecodeLength(bytecode_)); + DCHECK(argument_byte_length == 1 || argument_byte_length == 2 || + argument_byte_length == 4); + + int absolute_offset = start_offset_ + argument_offset; + + argument_check_->push_back(BytecodeArgumentCheck{ + absolute_offset, argument_byte_length, check_byte_offset}); + + return *this; +} + +BytecodeSequenceNode& BytecodeSequenceNode::IfArgumentEqualsValueAtOffset( + int argument_offset, int argument_byte_length, + int other_bytecode_index_in_sequence, int other_argument_offset, + int other_argument_byte_length) { + DCHECK_LT(argument_offset, RegExpBytecodeLength(bytecode_)); + DCHECK_LE(other_bytecode_index_in_sequence, index_in_sequence_); + DCHECK_EQ(argument_byte_length, other_argument_byte_length); + + BytecodeSequenceNode& ref_node = + GetNodeByIndexInSequence(other_bytecode_index_in_sequence); + DCHECK_LT(other_argument_offset, RegExpBytecodeLength(ref_node.bytecode_)); + + int absolute_offset = start_offset_ + argument_offset; + int other_absolute_offset = ref_node.start_offset_ + other_argument_offset; + + argument_check_->push_back( + BytecodeArgumentCheck{absolute_offset, argument_byte_length, + other_absolute_offset, other_argument_byte_length}); + + return *this; +} + +BytecodeSequenceNode& BytecodeSequenceNode::IgnoreArgument( + int bytecode_index_in_sequence, int argument_offset, + int argument_byte_length) { + DCHECK(IsSequence()); + DCHECK_LE(bytecode_index_in_sequence, index_in_sequence_); + + BytecodeSequenceNode& ref_node = + GetNodeByIndexInSequence(bytecode_index_in_sequence); + DCHECK_LT(argument_offset, RegExpBytecodeLength(ref_node.bytecode_)); + + int absolute_offset = ref_node.start_offset_ + argument_offset; + + argument_ignored_->push_back( + BytecodeArgument{absolute_offset, argument_byte_length}); + + return *this; +} + +bool BytecodeSequenceNode::CheckArguments(const uint8_t* bytecode, int pc) { + bool is_valid = true; + for (auto check_iter = argument_check_->begin(); + check_iter != argument_check_->end() && is_valid; check_iter++) { + auto value = + GetArgumentValue(bytecode, pc + check_iter->offset, check_iter->length); + if (check_iter->type == BytecodeArgumentCheck::kCheckAddress) { + is_valid &= value == pc + check_iter->check_offset; + } else if (check_iter->type == BytecodeArgumentCheck::kCheckValue) { + auto other_value = GetArgumentValue( + bytecode, pc + check_iter->check_offset, check_iter->check_length); + is_valid &= value == other_value; + } else { + UNREACHABLE(); + } + } + return is_valid; +} + +bool BytecodeSequenceNode::IsSequence() const { + return bytecode_replacement_ != kDummyBytecode; +} + +int BytecodeSequenceNode::SequenceLength() const { + return start_offset_ + RegExpBytecodeLength(bytecode_); +} + +int BytecodeSequenceNode::OptimizedBytecode() const { + return bytecode_replacement_; +} + +BytecodeSequenceNode* BytecodeSequenceNode::Find(int bytecode) const { + auto found = children_.find(bytecode); + if (found == children_.end()) return nullptr; + return found->second; +} + +size_t BytecodeSequenceNode::ArgumentSize() const { + DCHECK(IsSequence()); + return argument_mapping_->size(); +} + +BytecodeArgumentMapping BytecodeSequenceNode::ArgumentMapping( + size_t index) const { + DCHECK(IsSequence()); + DCHECK(argument_mapping_ != nullptr); + DCHECK_LT(index, argument_mapping_->size()); + + return argument_mapping_->at(index); +} + +ZoneLinkedList<BytecodeArgument>::iterator +BytecodeSequenceNode::ArgumentIgnoredBegin() const { + DCHECK(IsSequence()); + DCHECK(argument_ignored_ != nullptr); + return argument_ignored_->begin(); +} + +ZoneLinkedList<BytecodeArgument>::iterator +BytecodeSequenceNode::ArgumentIgnoredEnd() const { + DCHECK(IsSequence()); + DCHECK(argument_ignored_ != nullptr); + return argument_ignored_->end(); +} + +bool BytecodeSequenceNode::HasIgnoredArguments() const { + return argument_ignored_ != nullptr; +} + +BytecodeSequenceNode& BytecodeSequenceNode::GetNodeByIndexInSequence( + int index_in_sequence) { + DCHECK_LE(index_in_sequence, index_in_sequence_); + + if (index_in_sequence < index_in_sequence_) { + DCHECK(parent_ != nullptr); + return parent_->GetNodeByIndexInSequence(index_in_sequence); + } else { + return *this; + } +} + +Zone* BytecodeSequenceNode::zone() const { return zone_; } + +RegExpBytecodePeephole::RegExpBytecodePeephole( + Zone* zone, size_t buffer_size, + const ZoneUnorderedMap<int, int>& jump_edges) + : optimized_bytecode_buffer_(zone), + sequences_(zone->New<BytecodeSequenceNode>( + BytecodeSequenceNode::kDummyBytecode, zone)), + jump_edges_(zone), + jump_edges_mapped_(zone), + jump_usage_counts_(zone), + jump_source_fixups_(zone), + jump_destination_fixups_(zone), + zone_(zone) { + optimized_bytecode_buffer_.reserve(buffer_size); + PrepareJumpStructures(jump_edges); + DefineStandardSequences(); + // Sentinel fixups at beginning of bytecode (position -1) so we don't have to + // check for end of iterator inside the fixup loop. + // In general fixups are deltas of original offsets of jump + // sources/destinations (in the old bytecode) to find them in the new + // bytecode. All jump targets are fixed after the new bytecode is fully + // emitted in the internal buffer. + AddSentinelFixups(-1); + // Sentinel fixups at end of (old) bytecode so we don't have to check for + // end of iterator inside the fixup loop. + DCHECK_LE(buffer_size, std::numeric_limits<int>::max()); + AddSentinelFixups(static_cast<int>(buffer_size)); +} + +void RegExpBytecodePeephole::DefineStandardSequences() { + // Commonly used sequences can be found by creating regexp bytecode traces + // (--trace-regexp-bytecodes) and using v8/tools/regexp-sequences.py. + CreateSequence(BC_LOAD_CURRENT_CHAR) + .FollowedBy(BC_CHECK_BIT_IN_TABLE) + .FollowedBy(BC_ADVANCE_CP_AND_GOTO) + // Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the + // first bytecode in this sequence. + .IfArgumentEqualsOffset(4, 4, 0) + .ReplaceWith(BC_SKIP_UNTIL_BIT_IN_TABLE) + .MapArgument(0, 1, 3) // load offset + .MapArgument(2, 1, 3, 4) // advance by + .MapArgument(1, 8, 16) // bit table + .MapArgument(1, 4, 4) // goto when match + .MapArgument(0, 4, 4) // goto on failure + .IgnoreArgument(2, 4, 4); // loop jump + + CreateSequence(BC_CHECK_CURRENT_POSITION) + .FollowedBy(BC_LOAD_CURRENT_CHAR_UNCHECKED) + .FollowedBy(BC_CHECK_CHAR) + .FollowedBy(BC_ADVANCE_CP_AND_GOTO) + // Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the + // first bytecode in this sequence. + .IfArgumentEqualsOffset(4, 4, 0) + .ReplaceWith(BC_SKIP_UNTIL_CHAR_POS_CHECKED) + .MapArgument(1, 1, 3) // load offset + .MapArgument(3, 1, 3, 2) // advance_by + .MapArgument(2, 1, 3, 2) // c + .MapArgument(0, 1, 3, 4) // eats at least + .MapArgument(2, 4, 4) // goto when match + .MapArgument(0, 4, 4) // goto on failure + .IgnoreArgument(3, 4, 4); // loop jump + + CreateSequence(BC_CHECK_CURRENT_POSITION) + .FollowedBy(BC_LOAD_CURRENT_CHAR_UNCHECKED) + .FollowedBy(BC_AND_CHECK_CHAR) + .FollowedBy(BC_ADVANCE_CP_AND_GOTO) + // Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the + // first bytecode in this sequence. + .IfArgumentEqualsOffset(4, 4, 0) + .ReplaceWith(BC_SKIP_UNTIL_CHAR_AND) + .MapArgument(1, 1, 3) // load offset + .MapArgument(3, 1, 3, 2) // advance_by + .MapArgument(2, 1, 3, 2) // c + .MapArgument(2, 4, 4) // mask + .MapArgument(0, 1, 3, 4) // eats at least + .MapArgument(2, 8, 4) // goto when match + .MapArgument(0, 4, 4) // goto on failure + .IgnoreArgument(3, 4, 4); // loop jump + + // TODO(pthier): It might make sense for short sequences like this one to only + // optimize them if the resulting optimization is not longer than the current + // one. This could be the case if there are jumps inside the sequence and we + // have to replicate parts of the sequence. A method to mark such sequences + // might be useful. + CreateSequence(BC_LOAD_CURRENT_CHAR) + .FollowedBy(BC_CHECK_CHAR) + .FollowedBy(BC_ADVANCE_CP_AND_GOTO) + // Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the + // first bytecode in this sequence. + .IfArgumentEqualsOffset(4, 4, 0) + .ReplaceWith(BC_SKIP_UNTIL_CHAR) + .MapArgument(0, 1, 3) // load offset + .MapArgument(2, 1, 3, 2) // advance by + .MapArgument(1, 1, 3, 2) // character + .MapArgument(1, 4, 4) // goto when match + .MapArgument(0, 4, 4) // goto on failure + .IgnoreArgument(2, 4, 4); // loop jump + + CreateSequence(BC_LOAD_CURRENT_CHAR) + .FollowedBy(BC_CHECK_CHAR) + .FollowedBy(BC_CHECK_CHAR) + // Sequence is only valid if the jump targets of both CHECK_CHAR bytecodes + // are equal. + .IfArgumentEqualsValueAtOffset(4, 4, 1, 4, 4) + .FollowedBy(BC_ADVANCE_CP_AND_GOTO) + // Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the + // first bytecode in this sequence. + .IfArgumentEqualsOffset(4, 4, 0) + .ReplaceWith(BC_SKIP_UNTIL_CHAR_OR_CHAR) + .MapArgument(0, 1, 3) // load offset + .MapArgument(3, 1, 3, 4) // advance by + .MapArgument(1, 1, 3, 2) // character 1 + .MapArgument(2, 1, 3, 2) // character 2 + .MapArgument(1, 4, 4) // goto when match + .MapArgument(0, 4, 4) // goto on failure + .IgnoreArgument(2, 4, 4) // goto when match 2 + .IgnoreArgument(3, 4, 4); // loop jump + + CreateSequence(BC_LOAD_CURRENT_CHAR) + .FollowedBy(BC_CHECK_GT) + // Sequence is only valid if the jump target of CHECK_GT is the first + // bytecode AFTER the whole sequence. + .IfArgumentEqualsOffset(4, 4, 56) + .FollowedBy(BC_CHECK_BIT_IN_TABLE) + // Sequence is only valid if the jump target of CHECK_BIT_IN_TABLE is + // the ADVANCE_CP_AND_GOTO bytecode at the end of the sequence. + .IfArgumentEqualsOffset(4, 4, 48) + .FollowedBy(BC_GOTO) + // Sequence is only valid if the jump target of GOTO is the same as the + // jump target of CHECK_GT (i.e. both jump to the first bytecode AFTER the + // whole sequence. + .IfArgumentEqualsValueAtOffset(4, 4, 1, 4, 4) + .FollowedBy(BC_ADVANCE_CP_AND_GOTO) + // Sequence is only valid if the jump target of ADVANCE_CP_AND_GOTO is the + // first bytecode in this sequence. + .IfArgumentEqualsOffset(4, 4, 0) + .ReplaceWith(BC_SKIP_UNTIL_GT_OR_NOT_BIT_IN_TABLE) + .MapArgument(0, 1, 3) // load offset + .MapArgument(4, 1, 3, 2) // advance by + .MapArgument(1, 1, 3, 2) // character + .MapArgument(2, 8, 16) // bit table + .MapArgument(1, 4, 4) // goto when match + .MapArgument(0, 4, 4) // goto on failure + .IgnoreArgument(2, 4, 4) // indirect loop jump + .IgnoreArgument(3, 4, 4) // jump out of loop + .IgnoreArgument(4, 4, 4); // loop jump +} + +bool RegExpBytecodePeephole::OptimizeBytecode(const uint8_t* bytecode, + int length) { + int old_pc = 0; + bool did_optimize = false; + + while (old_pc < length) { + int replaced_len = TryOptimizeSequence(bytecode, length, old_pc); + if (replaced_len > 0) { + old_pc += replaced_len; + did_optimize = true; + } else { + int bc = bytecode[old_pc]; + int bc_len = RegExpBytecodeLength(bc); + CopyRangeToOutput(bytecode, old_pc, bc_len); + old_pc += bc_len; + } + } + + if (did_optimize) { + FixJumps(); + } + + return did_optimize; +} + +void RegExpBytecodePeephole::CopyOptimizedBytecode(uint8_t* to_address) const { + MemCopy(to_address, &(*optimized_bytecode_buffer_.begin()), Length()); +} + +int RegExpBytecodePeephole::Length() const { return pc(); } + +BytecodeSequenceNode& RegExpBytecodePeephole::CreateSequence(int bytecode) { + DCHECK(sequences_ != nullptr); + DCHECK(0 <= bytecode && bytecode < kRegExpBytecodeCount); + + return sequences_->FollowedBy(bytecode); +} + +int RegExpBytecodePeephole::TryOptimizeSequence(const uint8_t* bytecode, + int bytecode_length, + int start_pc) { + BytecodeSequenceNode* seq_node = sequences_; + BytecodeSequenceNode* valid_seq_end = nullptr; + + int current_pc = start_pc; + + // Check for the longest valid sequence matching any of the pre-defined + // sequences in the Trie data structure. + while (current_pc < bytecode_length) { + seq_node = seq_node->Find(bytecode[current_pc]); + if (seq_node == nullptr) break; + if (!seq_node->CheckArguments(bytecode, start_pc)) break; + + if (seq_node->IsSequence()) valid_seq_end = seq_node; + current_pc += RegExpBytecodeLength(bytecode[current_pc]); + } + + if (valid_seq_end) { + EmitOptimization(start_pc, bytecode, *valid_seq_end); + return valid_seq_end->SequenceLength(); + } + + return 0; +} + +void RegExpBytecodePeephole::EmitOptimization( + int start_pc, const uint8_t* bytecode, + const BytecodeSequenceNode& last_node) { +#ifdef DEBUG + int optimized_start_pc = pc(); +#endif + // Jump sources that are mapped or marked as unused will be deleted at the end + // of this method. We don't delete them immediately as we might need the + // information when we have to preserve bytecodes at the end. + // TODO(pthier): Replace with a stack-allocated data structure. + ZoneLinkedList<int> delete_jumps = ZoneLinkedList<int>(zone()); + + uint32_t bc = last_node.OptimizedBytecode(); + EmitValue(bc); + + for (size_t arg = 0; arg < last_node.ArgumentSize(); arg++) { + BytecodeArgumentMapping arg_map = last_node.ArgumentMapping(arg); + int arg_pos = start_pc + arg_map.offset; + // If we map any jump source we mark the old source for deletion and insert + // a new jump. + auto jump_edge_iter = jump_edges_.find(arg_pos); + if (jump_edge_iter != jump_edges_.end()) { + int jump_source = jump_edge_iter->first; + int jump_destination = jump_edge_iter->second; + // Add new jump edge add current position. + jump_edges_mapped_.emplace(Length(), jump_destination); + // Mark old jump edge for deletion. + delete_jumps.push_back(jump_source); + // Decrement usage count of jump destination. + auto jump_count_iter = jump_usage_counts_.find(jump_destination); + DCHECK(jump_count_iter != jump_usage_counts_.end()); + int& usage_count = jump_count_iter->second; + --usage_count; + } + // TODO(pthier): DCHECK that mapped arguments are never sources of jumps + // to destinations inside the sequence. + EmitArgument(start_pc, bytecode, arg_map); + } + DCHECK_EQ(pc(), optimized_start_pc + + RegExpBytecodeLength(last_node.OptimizedBytecode())); + + // Remove jumps from arguments we ignore. + if (last_node.HasIgnoredArguments()) { + for (auto ignored_arg = last_node.ArgumentIgnoredBegin(); + ignored_arg != last_node.ArgumentIgnoredEnd(); ignored_arg++) { + auto jump_edge_iter = jump_edges_.find(start_pc + ignored_arg->offset); + if (jump_edge_iter != jump_edges_.end()) { + int jump_source = jump_edge_iter->first; + int jump_destination = jump_edge_iter->second; + // Mark old jump edge for deletion. + delete_jumps.push_back(jump_source); + // Decrement usage count of jump destination. + auto jump_count_iter = jump_usage_counts_.find(jump_destination); + DCHECK(jump_count_iter != jump_usage_counts_.end()); + int& usage_count = jump_count_iter->second; + --usage_count; + } + } + } + + int fixup_length = RegExpBytecodeLength(bc) - last_node.SequenceLength(); + + // Check if there are any jumps inside the old sequence. + // If so we have to keep the bytecodes that are jumped to around. + auto jump_destination_candidate = jump_usage_counts_.upper_bound(start_pc); + int jump_candidate_destination = jump_destination_candidate->first; + int jump_candidate_count = jump_destination_candidate->second; + // Jump destinations only jumped to from inside the sequence will be ignored. + while (jump_destination_candidate != jump_usage_counts_.end() && + jump_candidate_count == 0) { + ++jump_destination_candidate; + jump_candidate_destination = jump_destination_candidate->first; + jump_candidate_count = jump_destination_candidate->second; + } + + int preserve_from = start_pc + last_node.SequenceLength(); + if (jump_destination_candidate != jump_usage_counts_.end() && + jump_candidate_destination < start_pc + last_node.SequenceLength()) { + preserve_from = jump_candidate_destination; + // Check if any jump in the sequence we are preserving has a jump + // destination inside the optimized sequence before the current position we + // want to preserve. If so we have to preserve all bytecodes starting at + // this jump destination. + for (auto jump_iter = jump_edges_.lower_bound(preserve_from); + jump_iter != jump_edges_.end() && + jump_iter->first /* jump source */ < + start_pc + last_node.SequenceLength(); + ++jump_iter) { + int jump_destination = jump_iter->second; + if (jump_destination > start_pc && jump_destination < preserve_from) { + preserve_from = jump_destination; + } + } + + // We preserve everything to the end of the sequence. This is conservative + // since it would be enough to preserve all bytecudes up to an unconditional + // jump. + int preserve_length = start_pc + last_node.SequenceLength() - preserve_from; + fixup_length += preserve_length; + // Jumps after the start of the preserved sequence need fixup. + AddJumpSourceFixup(fixup_length, + start_pc + last_node.SequenceLength() - preserve_length); + // All jump targets after the start of the optimized sequence need to be + // fixed relative to the length of the optimized sequence including + // bytecodes we preserved. + AddJumpDestinationFixup(fixup_length, start_pc + 1); + // Jumps to the sequence we preserved need absolute fixup as they could + // occur before or after the sequence. + SetJumpDestinationFixup(pc() - preserve_from, preserve_from); + CopyRangeToOutput(bytecode, preserve_from, preserve_length); + } else { + AddJumpDestinationFixup(fixup_length, start_pc + 1); + // Jumps after the end of the old sequence need fixup. + AddJumpSourceFixup(fixup_length, start_pc + last_node.SequenceLength()); + } + + // Delete jumps we definitely don't need anymore + for (int del : delete_jumps) { + if (del < preserve_from) { + jump_edges_.erase(del); + } + } +} + +void RegExpBytecodePeephole::AddJumpSourceFixup(int fixup, int pos) { + auto previous_fixup = jump_source_fixups_.lower_bound(pos); + DCHECK(previous_fixup != jump_source_fixups_.end()); + DCHECK(previous_fixup != jump_source_fixups_.begin()); + + int previous_fixup_value = (--previous_fixup)->second; + jump_source_fixups_[pos] = previous_fixup_value + fixup; +} + +void RegExpBytecodePeephole::AddJumpDestinationFixup(int fixup, int pos) { + auto previous_fixup = jump_destination_fixups_.lower_bound(pos); + DCHECK(previous_fixup != jump_destination_fixups_.end()); + DCHECK(previous_fixup != jump_destination_fixups_.begin()); + + int previous_fixup_value = (--previous_fixup)->second; + jump_destination_fixups_[pos] = previous_fixup_value + fixup; +} + +void RegExpBytecodePeephole::SetJumpDestinationFixup(int fixup, int pos) { + auto previous_fixup = jump_destination_fixups_.lower_bound(pos); + DCHECK(previous_fixup != jump_destination_fixups_.end()); + DCHECK(previous_fixup != jump_destination_fixups_.begin()); + + int previous_fixup_value = (--previous_fixup)->second; + jump_destination_fixups_.emplace(pos, fixup); + jump_destination_fixups_.emplace(pos + 1, previous_fixup_value); +} + +void RegExpBytecodePeephole::PrepareJumpStructures( + const ZoneUnorderedMap<int, int>& jump_edges) { + for (auto jump_edge : jump_edges) { + int jump_source = jump_edge.first; + int jump_destination = jump_edge.second; + + jump_edges_.emplace(jump_source, jump_destination); + jump_usage_counts_[jump_destination]++; + } +} + +void RegExpBytecodePeephole::FixJumps() { + int position_fixup = 0; + // Next position where fixup changes. + auto next_source_fixup = jump_source_fixups_.lower_bound(0); + int next_source_fixup_offset = next_source_fixup->first; + int next_source_fixup_value = next_source_fixup->second; + + for (auto jump_edge : jump_edges_) { + int jump_source = jump_edge.first; + int jump_destination = jump_edge.second; + while (jump_source >= next_source_fixup_offset) { + position_fixup = next_source_fixup_value; + ++next_source_fixup; + next_source_fixup_offset = next_source_fixup->first; + next_source_fixup_value = next_source_fixup->second; + } + jump_source += position_fixup; + + FixJump(jump_source, jump_destination); + } + + // Mapped jump edges don't need source fixups, as the position already is an + // offset in the new bytecode. + for (auto jump_edge : jump_edges_mapped_) { + int jump_source = jump_edge.first; + int jump_destination = jump_edge.second; + + FixJump(jump_source, jump_destination); + } +} + +void RegExpBytecodePeephole::FixJump(int jump_source, int jump_destination) { + int fixed_jump_destination = + jump_destination + + (--jump_destination_fixups_.upper_bound(jump_destination))->second; + DCHECK_LT(fixed_jump_destination, Length()); +#ifdef DEBUG + // TODO(pthier): This check could be better if we track the bytecodes + // actually used and check if we jump to one of them. + uint8_t jump_bc = optimized_bytecode_buffer_[fixed_jump_destination]; + DCHECK_GT(jump_bc, 0); + DCHECK_LT(jump_bc, kRegExpBytecodeCount); +#endif + + if (jump_destination != fixed_jump_destination) { + OverwriteValue<uint32_t>(jump_source, fixed_jump_destination); + } +} + +void RegExpBytecodePeephole::AddSentinelFixups(int pos) { + jump_source_fixups_.emplace(pos, 0); + jump_destination_fixups_.emplace(pos, 0); +} + +template <typename T> +void RegExpBytecodePeephole::EmitValue(T value) { + DCHECK(optimized_bytecode_buffer_.begin() + pc() == + optimized_bytecode_buffer_.end()); + uint8_t* value_byte_iter = reinterpret_cast<uint8_t*>(&value); + optimized_bytecode_buffer_.insert(optimized_bytecode_buffer_.end(), + value_byte_iter, + value_byte_iter + sizeof(T)); +} + +template <typename T> +void RegExpBytecodePeephole::OverwriteValue(int offset, T value) { + uint8_t* value_byte_iter = reinterpret_cast<uint8_t*>(&value); + uint8_t* value_byte_iter_end = value_byte_iter + sizeof(T); + while (value_byte_iter < value_byte_iter_end) { + optimized_bytecode_buffer_[offset++] = *value_byte_iter++; + } +} + +void RegExpBytecodePeephole::CopyRangeToOutput(const uint8_t* orig_bytecode, + int start, int length) { + DCHECK(optimized_bytecode_buffer_.begin() + pc() == + optimized_bytecode_buffer_.end()); + optimized_bytecode_buffer_.insert(optimized_bytecode_buffer_.end(), + orig_bytecode + start, + orig_bytecode + start + length); +} + +void RegExpBytecodePeephole::SetRange(uint8_t value, int count) { + DCHECK(optimized_bytecode_buffer_.begin() + pc() == + optimized_bytecode_buffer_.end()); + optimized_bytecode_buffer_.insert(optimized_bytecode_buffer_.end(), count, + value); +} + +void RegExpBytecodePeephole::EmitArgument(int start_pc, const uint8_t* bytecode, + BytecodeArgumentMapping arg) { + int arg_pos = start_pc + arg.offset; + switch (arg.length) { + case 1: + DCHECK_EQ(arg.new_length, arg.length); + EmitValue(GetValue<uint8_t>(bytecode, arg_pos)); + break; + case 2: + DCHECK_EQ(arg.new_length, arg.length); + EmitValue(GetValue<uint16_t>(bytecode, arg_pos)); + break; + case 3: { + // Length 3 only occurs in 'packed' arguments where the lowermost byte is + // the current bytecode, and the remaining 3 bytes are the packed value. + // + // We load 4 bytes from position - 1 and shift out the bytecode. +#ifdef V8_TARGET_BIG_ENDIAN + UNIMPLEMENTED(); + int32_t val = 0; +#else + int32_t val = GetValue<int32_t>(bytecode, arg_pos - 1) >> kBitsPerByte; +#endif // V8_TARGET_BIG_ENDIAN + + switch (arg.new_length) { + case 2: + EmitValue<uint16_t>(val); + break; + case 3: { + // Pack with previously emitted value. + auto prev_val = + GetValue<int32_t>(&(*optimized_bytecode_buffer_.begin()), + Length() - sizeof(uint32_t)); +#ifdef V8_TARGET_BIG_ENDIAN + UNIMPLEMENTED(); + USE(prev_val); +#else + DCHECK_EQ(prev_val & 0xFFFFFF00, 0); + OverwriteValue<uint32_t>( + pc() - sizeof(uint32_t), + (static_cast<uint32_t>(val) << 8) | (prev_val & 0xFF)); +#endif // V8_TARGET_BIG_ENDIAN + break; + } + case 4: + EmitValue<uint32_t>(val); + break; + } + break; + } + case 4: + DCHECK_EQ(arg.new_length, arg.length); + EmitValue(GetValue<uint32_t>(bytecode, arg_pos)); + break; + case 8: + DCHECK_EQ(arg.new_length, arg.length); + EmitValue(GetValue<uint64_t>(bytecode, arg_pos)); + break; + default: + CopyRangeToOutput(bytecode, arg_pos, + std::min(arg.length, arg.new_length)); + if (arg.length < arg.new_length) { + SetRange(0x00, arg.new_length - arg.length); + } + break; + } +} + +int RegExpBytecodePeephole::pc() const { + DCHECK_LE(optimized_bytecode_buffer_.size(), std::numeric_limits<int>::max()); + return static_cast<int>(optimized_bytecode_buffer_.size()); +} + +Zone* RegExpBytecodePeephole::zone() const { return zone_; } + +} // namespace + +// static +Handle<ByteArray> RegExpBytecodePeepholeOptimization::OptimizeBytecode( + Isolate* isolate, Zone* zone, Handle<String> source, + const uint8_t* bytecode, int length, + const ZoneUnorderedMap<int, int>& jump_edges) { + RegExpBytecodePeephole peephole(zone, length, jump_edges); + bool did_optimize = peephole.OptimizeBytecode(bytecode, length); + Handle<ByteArray> array = isolate->factory()->NewByteArray(peephole.Length()); + peephole.CopyOptimizedBytecode(array->GetDataStartAddress()); + + 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(), + source->ToCString().get()); + } + + return array; +} + +} // namespace internal +} // namespace v8 diff --git a/js/src/irregexp/imported/regexp-bytecode-peephole.h b/js/src/irregexp/imported/regexp-bytecode-peephole.h new file mode 100644 index 0000000000..c1f0d66cca --- /dev/null +++ b/js/src/irregexp/imported/regexp-bytecode-peephole.h @@ -0,0 +1,31 @@ +// 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. + +#ifndef V8_REGEXP_REGEXP_BYTECODE_PEEPHOLE_H_ +#define V8_REGEXP_REGEXP_BYTECODE_PEEPHOLE_H_ + +#include "irregexp/RegExpShim.h" + +namespace v8 { +namespace internal { + +class ByteArray; + +// Peephole optimization for regexp interpreter bytecode. +// Pre-defined bytecode sequences occuring in the bytecode generated by the +// RegExpBytecodeGenerator can be optimized into a single bytecode. +class RegExpBytecodePeepholeOptimization : public AllStatic { + public: + // Performs peephole optimization on the given bytecode and returns the + // optimized bytecode. + static Handle<ByteArray> OptimizeBytecode( + Isolate* isolate, Zone* zone, Handle<String> source, + const uint8_t* bytecode, int length, + const ZoneUnorderedMap<int, int>& jump_edges); +}; + +} // namespace internal +} // namespace v8 + +#endif // V8_REGEXP_REGEXP_BYTECODE_PEEPHOLE_H_ diff --git a/js/src/irregexp/imported/regexp-bytecodes.cc b/js/src/irregexp/imported/regexp-bytecodes.cc new file mode 100644 index 0000000000..d3a39ab5dc --- /dev/null +++ b/js/src/irregexp/imported/regexp-bytecodes.cc @@ -0,0 +1,47 @@ +// 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-bytecodes.h" + +#include <cctype> + + +namespace v8 { +namespace internal { + +void RegExpBytecodeDisassembleSingle(const uint8_t* code_base, + const uint8_t* pc) { + int bytecode = *reinterpret_cast<const int32_t*>(pc) & BYTECODE_MASK; + PrintF("%s", RegExpBytecodeName(bytecode)); + + // Args and the bytecode as hex. + for (int i = 0; i < RegExpBytecodeLength(bytecode); i++) { + PrintF(", %02x", pc[i]); + } + PrintF(" "); + + // Args as ascii. + for (int i = 1; i < RegExpBytecodeLength(bytecode); i++) { + unsigned char b = pc[i]; + PrintF("%c", std::isprint(b) ? b : '.'); + } + PrintF("\n"); +} + +void RegExpBytecodeDisassemble(const uint8_t* code_base, int length, + const char* pattern) { + PrintF("[generated bytecode for regexp pattern: '%s']\n", pattern); + + ptrdiff_t offset = 0; + + while (offset < length) { + const uint8_t* const pc = code_base + offset; + PrintF("%p %4" V8PRIxPTRDIFF " ", pc, offset); + RegExpBytecodeDisassembleSingle(code_base, pc); + offset += RegExpBytecodeLength(*pc); + } +} + +} // namespace internal +} // namespace v8 diff --git a/js/src/irregexp/imported/regexp-bytecodes.h b/js/src/irregexp/imported/regexp-bytecodes.h new file mode 100644 index 0000000000..95103338e1 --- /dev/null +++ b/js/src/irregexp/imported/regexp-bytecodes.h @@ -0,0 +1,258 @@ +// Copyright 2011 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. + +#ifndef V8_REGEXP_REGEXP_BYTECODES_H_ +#define V8_REGEXP_REGEXP_BYTECODES_H_ + +#include "irregexp/RegExpShim.h" + +namespace v8 { +namespace internal { + +// Maximum number of bytecodes that will be used (next power of 2 of actually +// defined bytecodes). +// All slots between the last actually defined bytecode and maximum id will be +// filled with BREAKs, indicating an invalid operation. This way using +// BYTECODE_MASK guarantees no OOB access to the dispatch table. +constexpr int kRegExpPaddedBytecodeCount = 1 << 6; +constexpr int BYTECODE_MASK = kRegExpPaddedBytecodeCount - 1; +// The first argument is packed in with the byte code in one word, but so it +// has 24 bits, but it can be positive and negative so only use 23 bits for +// positive values. +const unsigned int MAX_FIRST_ARG = 0x7fffffu; +const int BYTECODE_SHIFT = 8; +static_assert(1 << BYTECODE_SHIFT > BYTECODE_MASK); + +// The list of bytecodes, in format: V(Name, Code, ByteLength). +// TODO(pthier): Argument offsets of bytecodes should be easily accessible by +// name or at least by position. +// TODO(jgruber): More precise types (e.g. int32/uint32 instead of value32). +#define BYTECODE_ITERATOR(V) \ + V(BREAK, 0, 4) /* bc8 */ \ + V(PUSH_CP, 1, 4) /* bc8 pad24 */ \ + V(PUSH_BT, 2, 8) /* bc8 pad24 offset32 */ \ + V(PUSH_REGISTER, 3, 4) /* bc8 reg_idx24 */ \ + V(SET_REGISTER_TO_CP, 4, 8) /* bc8 reg_idx24 offset32 */ \ + V(SET_CP_TO_REGISTER, 5, 4) /* bc8 reg_idx24 */ \ + V(SET_REGISTER_TO_SP, 6, 4) /* bc8 reg_idx24 */ \ + V(SET_SP_TO_REGISTER, 7, 4) /* bc8 reg_idx24 */ \ + V(SET_REGISTER, 8, 8) /* bc8 reg_idx24 value32 */ \ + V(ADVANCE_REGISTER, 9, 8) /* bc8 reg_idx24 value32 */ \ + V(POP_CP, 10, 4) /* bc8 pad24 */ \ + V(POP_BT, 11, 4) /* bc8 pad24 */ \ + V(POP_REGISTER, 12, 4) /* bc8 reg_idx24 */ \ + V(FAIL, 13, 4) /* bc8 pad24 */ \ + V(SUCCEED, 14, 4) /* bc8 pad24 */ \ + V(ADVANCE_CP, 15, 4) /* bc8 offset24 */ \ + /* Jump to another bytecode given its offset. */ \ + /* Bit Layout: */ \ + /* 0x00 - 0x07: 0x10 (fixed) Bytecode */ \ + /* 0x08 - 0x1F: 0x00 (unused) Padding */ \ + /* 0x20 - 0x3F: Address of bytecode to jump to */ \ + V(GOTO, 16, 8) /* bc8 pad24 addr32 */ \ + /* Check if offset is in range and load character at given offset. */ \ + /* Bit Layout: */ \ + /* 0x00 - 0x07: 0x11 (fixed) Bytecode */ \ + /* 0x08 - 0x1F: Offset from current position */ \ + /* 0x20 - 0x3F: Address of bytecode when load is out of range */ \ + V(LOAD_CURRENT_CHAR, 17, 8) /* bc8 offset24 addr32 */ \ + /* Load character at given offset without range checks. */ \ + /* Bit Layout: */ \ + /* 0x00 - 0x07: 0x12 (fixed) Bytecode */ \ + /* 0x08 - 0x1F: Offset from current position */ \ + V(LOAD_CURRENT_CHAR_UNCHECKED, 18, 4) /* bc8 offset24 */ \ + V(LOAD_2_CURRENT_CHARS, 19, 8) /* bc8 offset24 addr32 */ \ + V(LOAD_2_CURRENT_CHARS_UNCHECKED, 20, 4) /* bc8 offset24 */ \ + V(LOAD_4_CURRENT_CHARS, 21, 8) /* bc8 offset24 addr32 */ \ + V(LOAD_4_CURRENT_CHARS_UNCHECKED, 22, 4) /* bc8 offset24 */ \ + V(CHECK_4_CHARS, 23, 12) /* bc8 pad24 uint32 addr32 */ \ + /* Check if current character is equal to a given character */ \ + /* Bit Layout: */ \ + /* 0x00 - 0x07: 0x19 (fixed) Bytecode */ \ + /* 0x08 - 0x0F: 0x00 (unused) Padding */ \ + /* 0x10 - 0x1F: Character to check */ \ + /* 0x20 - 0x3F: Address of bytecode when matched */ \ + V(CHECK_CHAR, 24, 8) /* bc8 pad8 uint16 addr32 */ \ + V(CHECK_NOT_4_CHARS, 25, 12) /* bc8 pad24 uint32 addr32 */ \ + V(CHECK_NOT_CHAR, 26, 8) /* bc8 pad8 uint16 addr32 */ \ + V(AND_CHECK_4_CHARS, 27, 16) /* bc8 pad24 uint32 uint32 addr32 */ \ + /* Checks if the current character combined with mask (bitwise and) */ \ + /* matches a character (e.g. used when two characters in a disjunction */ \ + /* differ by only a single bit */ \ + /* Bit Layout: */ \ + /* 0x00 - 0x07: 0x1c (fixed) Bytecode */ \ + /* 0x08 - 0x0F: 0x00 (unused) Padding */ \ + /* 0x10 - 0x1F: Character to match against (after mask aplied) */ \ + /* 0x20 - 0x3F: Bitmask bitwise and combined with current character */ \ + /* 0x40 - 0x5F: Address of bytecode when matched */ \ + V(AND_CHECK_CHAR, 28, 12) /* bc8 pad8 uint16 uint32 addr32 */ \ + V(AND_CHECK_NOT_4_CHARS, 29, 16) /* bc8 pad24 uint32 uint32 addr32 */ \ + V(AND_CHECK_NOT_CHAR, 30, 12) /* bc8 pad8 uint16 uint32 addr32 */ \ + V(MINUS_AND_CHECK_NOT_CHAR, 31, \ + 12) /* bc8 pad8 base::uc16 base::uc16 base::uc16 addr32 */ \ + V(CHECK_CHAR_IN_RANGE, 32, 12) /* bc8 pad24 base::uc16 base::uc16 addr32 */ \ + V(CHECK_CHAR_NOT_IN_RANGE, 33, \ + 12) /* bc8 pad24 base::uc16 base::uc16 addr32 */ \ + /* Checks if the current character matches any of the characters encoded */ \ + /* in a bit table. Similar to/inspired by boyer moore string search */ \ + /* Bit Layout: */ \ + /* 0x00 - 0x07: 0x22 (fixed) Bytecode */ \ + /* 0x08 - 0x1F: 0x00 (unused) Padding */ \ + /* 0x20 - 0x3F: Address of bytecode when bit is set */ \ + /* 0x40 - 0xBF: Bit table */ \ + V(CHECK_BIT_IN_TABLE, 34, 24) /* bc8 pad24 addr32 bits128 */ \ + V(CHECK_LT, 35, 8) /* bc8 pad8 base::uc16 addr32 */ \ + V(CHECK_GT, 36, 8) /* bc8 pad8 base::uc16 addr32 */ \ + V(CHECK_NOT_BACK_REF, 37, 8) /* bc8 reg_idx24 addr32 */ \ + V(CHECK_NOT_BACK_REF_NO_CASE, 38, 8) /* bc8 reg_idx24 addr32 */ \ + V(CHECK_NOT_BACK_REF_NO_CASE_UNICODE, 39, 8) \ + V(CHECK_NOT_BACK_REF_BACKWARD, 40, 8) /* bc8 reg_idx24 addr32 */ \ + V(CHECK_NOT_BACK_REF_NO_CASE_BACKWARD, 41, 8) /* bc8 reg_idx24 addr32 */ \ + V(CHECK_NOT_BACK_REF_NO_CASE_UNICODE_BACKWARD, 42, 8) \ + V(CHECK_NOT_REGS_EQUAL, 43, 12) /* bc8 regidx24 reg_idx32 addr32 */ \ + V(CHECK_REGISTER_LT, 44, 12) /* bc8 reg_idx24 value32 addr32 */ \ + V(CHECK_REGISTER_GE, 45, 12) /* bc8 reg_idx24 value32 addr32 */ \ + V(CHECK_REGISTER_EQ_POS, 46, 8) /* bc8 reg_idx24 addr32 */ \ + V(CHECK_AT_START, 47, 8) /* bc8 pad24 addr32 */ \ + V(CHECK_NOT_AT_START, 48, 8) /* bc8 offset24 addr32 */ \ + /* Checks if the current position matches top of backtrack stack */ \ + /* Bit Layout: */ \ + /* 0x00 - 0x07: 0x31 (fixed) Bytecode */ \ + /* 0x08 - 0x1F: 0x00 (unused) Padding */ \ + /* 0x20 - 0x3F: Address of bytecode when current matches tos */ \ + V(CHECK_GREEDY, 49, 8) /* bc8 pad24 addr32 */ \ + /* Advance character pointer by given offset and jump to another bytecode.*/ \ + /* Bit Layout: */ \ + /* 0x00 - 0x07: 0x32 (fixed) Bytecode */ \ + /* 0x08 - 0x1F: Number of characters to advance */ \ + /* 0x20 - 0x3F: Address of bytecode to jump to */ \ + V(ADVANCE_CP_AND_GOTO, 50, 8) /* bc8 offset24 addr32 */ \ + V(SET_CURRENT_POSITION_FROM_END, 51, 4) /* bc8 idx24 */ \ + /* Checks if current position + given offset is in range. */ \ + /* Bit Layout: */ \ + /* 0x00 - 0x07: 0x34 (fixed) Bytecode */ \ + /* 0x08 - 0x1F: Offset from current position */ \ + /* 0x20 - 0x3F: Address of bytecode when position is out of range */ \ + V(CHECK_CURRENT_POSITION, 52, 8) /* bc8 idx24 addr32 */ \ + /* Combination of: */ \ + /* LOAD_CURRENT_CHAR, CHECK_BIT_IN_TABLE and ADVANCE_CP_AND_GOTO */ \ + /* Emitted by RegExpBytecodePeepholeOptimization. */ \ + /* Bit Layout: */ \ + /* 0x00 - 0x07 0x35 (fixed) Bytecode */ \ + /* 0x08 - 0x1F Load character offset from current position */ \ + /* 0x20 - 0x3F Number of characters to advance */ \ + /* 0x40 - 0xBF Bit Table */ \ + /* 0xC0 - 0xDF Address of bytecode when character is matched */ \ + /* 0xE0 - 0xFF Address of bytecode when no match */ \ + V(SKIP_UNTIL_BIT_IN_TABLE, 53, 32) \ + /* Combination of: */ \ + /* CHECK_CURRENT_POSITION, LOAD_CURRENT_CHAR_UNCHECKED, AND_CHECK_CHAR */ \ + /* and ADVANCE_CP_AND_GOTO */ \ + /* Emitted by RegExpBytecodePeepholeOptimization. */ \ + /* Bit Layout: */ \ + /* 0x00 - 0x07 0x36 (fixed) Bytecode */ \ + /* 0x08 - 0x1F Load character offset from current position */ \ + /* 0x20 - 0x2F Number of characters to advance */ \ + /* 0x30 - 0x3F Character to match against (after mask applied) */ \ + /* 0x40 - 0x5F: Bitmask bitwise and combined with current character */ \ + /* 0x60 - 0x7F Minimum number of characters this pattern consumes */ \ + /* 0x80 - 0x9F Address of bytecode when character is matched */ \ + /* 0xA0 - 0xBF Address of bytecode when no match */ \ + V(SKIP_UNTIL_CHAR_AND, 54, 24) \ + /* Combination of: */ \ + /* LOAD_CURRENT_CHAR, CHECK_CHAR and ADVANCE_CP_AND_GOTO */ \ + /* Emitted by RegExpBytecodePeepholeOptimization. */ \ + /* Bit Layout: */ \ + /* 0x00 - 0x07 0x37 (fixed) Bytecode */ \ + /* 0x08 - 0x1F Load character offset from current position */ \ + /* 0x20 - 0x2F Number of characters to advance */ \ + /* 0x30 - 0x3F Character to match */ \ + /* 0x40 - 0x5F Address of bytecode when character is matched */ \ + /* 0x60 - 0x7F Address of bytecode when no match */ \ + V(SKIP_UNTIL_CHAR, 55, 16) \ + /* Combination of: */ \ + /* CHECK_CURRENT_POSITION, LOAD_CURRENT_CHAR_UNCHECKED, CHECK_CHAR */ \ + /* and ADVANCE_CP_AND_GOTO */ \ + /* Emitted by RegExpBytecodePeepholeOptimization. */ \ + /* Bit Layout: */ \ + /* 0x00 - 0x07 0x38 (fixed) Bytecode */ \ + /* 0x08 - 0x1F Load character offset from current position */ \ + /* 0x20 - 0x2F Number of characters to advance */ \ + /* 0x30 - 0x3F Character to match */ \ + /* 0x40 - 0x5F Minimum number of characters this pattern consumes */ \ + /* 0x60 - 0x7F Address of bytecode when character is matched */ \ + /* 0x80 - 0x9F Address of bytecode when no match */ \ + V(SKIP_UNTIL_CHAR_POS_CHECKED, 56, 20) \ + /* Combination of: */ \ + /* LOAD_CURRENT_CHAR, CHECK_CHAR, CHECK_CHAR and ADVANCE_CP_AND_GOTO */ \ + /* Emitted by RegExpBytecodePeepholeOptimization. */ \ + /* Bit Layout: */ \ + /* 0x00 - 0x07 0x39 (fixed) Bytecode */ \ + /* 0x08 - 0x1F Load character offset from current position */ \ + /* 0x20 - 0x3F Number of characters to advance */ \ + /* 0x40 - 0x4F Character to match */ \ + /* 0x50 - 0x5F Other Character to match */ \ + /* 0x60 - 0x7F Address of bytecode when either character is matched */ \ + /* 0x80 - 0x9F Address of bytecode when no match */ \ + V(SKIP_UNTIL_CHAR_OR_CHAR, 57, 20) \ + /* Combination of: */ \ + /* LOAD_CURRENT_CHAR, CHECK_GT, CHECK_BIT_IN_TABLE, GOTO and */ \ + /* and ADVANCE_CP_AND_GOTO */ \ + /* Emitted by RegExpBytecodePeepholeOptimization. */ \ + /* Bit Layout: */ \ + /* 0x00 - 0x07 0x3A (fixed) Bytecode */ \ + /* 0x08 - 0x1F Load character offset from current position */ \ + /* 0x20 - 0x2F Number of characters to advance */ \ + /* 0x30 - 0x3F Character to check if it is less than current char */ \ + /* 0x40 - 0xBF Bit Table */ \ + /* 0xC0 - 0xDF Address of bytecode when character is matched */ \ + /* 0xE0 - 0xFF Address of bytecode when no match */ \ + V(SKIP_UNTIL_GT_OR_NOT_BIT_IN_TABLE, 58, 32) + +#define COUNT(...) +1 +static constexpr int kRegExpBytecodeCount = BYTECODE_ITERATOR(COUNT); +#undef COUNT + +// Just making sure we assigned values above properly. They should be +// contiguous, strictly increasing, and start at 0. +// TODO(jgruber): Do not explicitly assign values, instead generate them +// implicitly from the list order. +static_assert(kRegExpBytecodeCount == 59); + +#define DECLARE_BYTECODES(name, code, length) \ + static constexpr int BC_##name = code; +BYTECODE_ITERATOR(DECLARE_BYTECODES) +#undef DECLARE_BYTECODES + +static constexpr int kRegExpBytecodeLengths[] = { +#define DECLARE_BYTECODE_LENGTH(name, code, length) length, + BYTECODE_ITERATOR(DECLARE_BYTECODE_LENGTH) +#undef DECLARE_BYTECODE_LENGTH +}; + +inline constexpr int RegExpBytecodeLength(int bytecode) { + DCHECK(base::IsInRange(bytecode, 0, kRegExpBytecodeCount - 1)); + return kRegExpBytecodeLengths[bytecode]; +} + +static constexpr const char* const kRegExpBytecodeNames[] = { +#define DECLARE_BYTECODE_NAME(name, ...) #name, + BYTECODE_ITERATOR(DECLARE_BYTECODE_NAME) +#undef DECLARE_BYTECODE_NAME +}; + +inline constexpr const char* RegExpBytecodeName(int bytecode) { + DCHECK(base::IsInRange(bytecode, 0, kRegExpBytecodeCount - 1)); + return kRegExpBytecodeNames[bytecode]; +} + +void RegExpBytecodeDisassembleSingle(const uint8_t* code_base, + const uint8_t* pc); +void RegExpBytecodeDisassemble(const uint8_t* code_base, int length, + const char* pattern); + +} // namespace internal +} // namespace v8 + +#endif // V8_REGEXP_REGEXP_BYTECODES_H_ diff --git a/js/src/irregexp/imported/regexp-compiler-tonode.cc b/js/src/irregexp/imported/regexp-compiler-tonode.cc new file mode 100644 index 0000000000..f5087bdb08 --- /dev/null +++ b/js/src/irregexp/imported/regexp-compiler-tonode.cc @@ -0,0 +1,2043 @@ +// 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" +#include "unicode/uniset.h" +#include "unicode/utypes.h" +#endif // V8_INTL_SUPPORT + +namespace v8 { +namespace internal { + +using namespace regexp_compiler_constants; // NOLINT(build/namespaces) + +constexpr base::uc32 kMaxCodePoint = 0x10ffff; +constexpr int kMaxUtf16CodeUnit = 0xffff; +constexpr uint32_t kMaxUtf16CodeUnitU = 0xffff; +constexpr int32_t kMaxOneByteCharCode = unibrow::Latin1::kMaxChar; + +// ------------------------------------------------------------------- +// Tree to graph conversion + +RegExpNode* RegExpAtom::ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) { + ZoneList<TextElement>* elms = + compiler->zone()->New<ZoneList<TextElement>>(1, compiler->zone()); + elms->Add(TextElement::Atom(this), compiler->zone()); + return compiler->zone()->New<TextNode>(elms, compiler->read_backward(), + on_success); +} + +RegExpNode* RegExpText::ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) { + return compiler->zone()->New<TextNode>(elements(), compiler->read_backward(), + on_success); +} + +namespace { + +bool CompareInverseRanges(ZoneList<CharacterRange>* ranges, + const int* special_class, int length) { + length--; // Remove final marker. + + DCHECK_EQ(kRangeEndMarker, special_class[length]); + DCHECK_NE(0, ranges->length()); + DCHECK_NE(0, length); + DCHECK_NE(0, special_class[0]); + + if (ranges->length() != (length >> 1) + 1) return false; + + CharacterRange range = ranges->at(0); + if (range.from() != 0) return false; + + for (int i = 0; i < length; i += 2) { + if (static_cast<base::uc32>(special_class[i]) != (range.to() + 1)) { + return false; + } + range = ranges->at((i >> 1) + 1); + if (static_cast<base::uc32>(special_class[i + 1]) != range.from()) { + return false; + } + } + + return range.to() == kMaxCodePoint; +} + +bool CompareRanges(ZoneList<CharacterRange>* ranges, const int* special_class, + int length) { + length--; // Remove final marker. + + DCHECK_EQ(kRangeEndMarker, special_class[length]); + if (ranges->length() * 2 != length) return false; + + for (int i = 0; i < length; i += 2) { + CharacterRange range = ranges->at(i >> 1); + if (range.from() != static_cast<base::uc32>(special_class[i]) || + range.to() != static_cast<base::uc32>(special_class[i + 1] - 1)) { + return false; + } + } + return true; +} + +} // namespace + +bool RegExpClassRanges::is_standard(Zone* zone) { + // TODO(lrn): Remove need for this function, by not throwing away information + // along the way. + if (is_negated()) { + return false; + } + if (set_.is_standard()) { + return true; + } + if (CompareRanges(set_.ranges(zone), kSpaceRanges, kSpaceRangeCount)) { + set_.set_standard_set_type(StandardCharacterSet::kWhitespace); + return true; + } + if (CompareInverseRanges(set_.ranges(zone), kSpaceRanges, kSpaceRangeCount)) { + set_.set_standard_set_type(StandardCharacterSet::kNotWhitespace); + return true; + } + if (CompareInverseRanges(set_.ranges(zone), kLineTerminatorRanges, + kLineTerminatorRangeCount)) { + set_.set_standard_set_type(StandardCharacterSet::kNotLineTerminator); + return true; + } + if (CompareRanges(set_.ranges(zone), kLineTerminatorRanges, + kLineTerminatorRangeCount)) { + set_.set_standard_set_type(StandardCharacterSet::kLineTerminator); + return true; + } + if (CompareRanges(set_.ranges(zone), kWordRanges, kWordRangeCount)) { + set_.set_standard_set_type(StandardCharacterSet::kWord); + return true; + } + if (CompareInverseRanges(set_.ranges(zone), kWordRanges, kWordRangeCount)) { + set_.set_standard_set_type(StandardCharacterSet::kNotWord); + return true; + } + return false; +} + +UnicodeRangeSplitter::UnicodeRangeSplitter(ZoneList<CharacterRange>* base) { + // The unicode range splitter categorizes given character ranges into: + // - Code points from the BMP representable by one code unit. + // - Code points outside the BMP that need to be split into + // surrogate pairs. + // - Lone lead surrogates. + // - Lone trail surrogates. + // Lone surrogates are valid code points, even though no actual characters. + // They require special matching to make sure we do not split surrogate pairs. + + for (int i = 0; i < base->length(); i++) AddRange(base->at(i)); +} + +void UnicodeRangeSplitter::AddRange(CharacterRange range) { + static constexpr base::uc32 kBmp1Start = 0; + static constexpr base::uc32 kBmp1End = kLeadSurrogateStart - 1; + static constexpr base::uc32 kBmp2Start = kTrailSurrogateEnd + 1; + static constexpr base::uc32 kBmp2End = kNonBmpStart - 1; + + // Ends are all inclusive. + static_assert(kBmp1Start == 0); + static_assert(kBmp1Start < kBmp1End); + static_assert(kBmp1End + 1 == kLeadSurrogateStart); + static_assert(kLeadSurrogateStart < kLeadSurrogateEnd); + static_assert(kLeadSurrogateEnd + 1 == kTrailSurrogateStart); + static_assert(kTrailSurrogateStart < kTrailSurrogateEnd); + static_assert(kTrailSurrogateEnd + 1 == kBmp2Start); + static_assert(kBmp2Start < kBmp2End); + static_assert(kBmp2End + 1 == kNonBmpStart); + static_assert(kNonBmpStart < kNonBmpEnd); + + static constexpr base::uc32 kStarts[] = { + kBmp1Start, kLeadSurrogateStart, kTrailSurrogateStart, + kBmp2Start, kNonBmpStart, + }; + + static constexpr base::uc32 kEnds[] = { + kBmp1End, kLeadSurrogateEnd, kTrailSurrogateEnd, kBmp2End, kNonBmpEnd, + }; + + CharacterRangeVector* const kTargets[] = { + &bmp_, &lead_surrogates_, &trail_surrogates_, &bmp_, &non_bmp_, + }; + + static constexpr int kCount = arraysize(kStarts); + static_assert(kCount == arraysize(kEnds)); + static_assert(kCount == arraysize(kTargets)); + + for (int i = 0; i < kCount; i++) { + if (kStarts[i] > range.to()) break; + const base::uc32 from = std::max(kStarts[i], range.from()); + const base::uc32 to = std::min(kEnds[i], range.to()); + if (from > to) continue; + kTargets[i]->emplace_back(CharacterRange::Range(from, to)); + } +} + +namespace { + +// Translates between new and old V8-isms (SmallVector, ZoneList). +ZoneList<CharacterRange>* ToCanonicalZoneList( + const UnicodeRangeSplitter::CharacterRangeVector* v, Zone* zone) { + if (v->empty()) return nullptr; + + ZoneList<CharacterRange>* result = + zone->New<ZoneList<CharacterRange>>(static_cast<int>(v->size()), zone); + for (size_t i = 0; i < v->size(); i++) { + result->Add(v->at(i), zone); + } + + CharacterRange::Canonicalize(result); + return result; +} + +void AddBmpCharacters(RegExpCompiler* compiler, ChoiceNode* result, + RegExpNode* on_success, UnicodeRangeSplitter* splitter) { + ZoneList<CharacterRange>* bmp = + ToCanonicalZoneList(splitter->bmp(), compiler->zone()); + if (bmp == nullptr) return; + result->AddAlternative(GuardedAlternative(TextNode::CreateForCharacterRanges( + compiler->zone(), bmp, compiler->read_backward(), on_success))); +} + +using UC16Range = uint32_t; // {from, to} packed into one uint32_t. +constexpr UC16Range ToUC16Range(base::uc16 from, base::uc16 to) { + return (static_cast<uint32_t>(from) << 16) | to; +} +constexpr base::uc16 ExtractFrom(UC16Range r) { + return static_cast<base::uc16>(r >> 16); +} +constexpr base::uc16 ExtractTo(UC16Range r) { + return static_cast<base::uc16>(r); +} + +void AddNonBmpSurrogatePairs(RegExpCompiler* compiler, ChoiceNode* result, + RegExpNode* on_success, + UnicodeRangeSplitter* splitter) { + DCHECK(!compiler->one_byte()); + Zone* const zone = compiler->zone(); + ZoneList<CharacterRange>* non_bmp = + ToCanonicalZoneList(splitter->non_bmp(), zone); + if (non_bmp == nullptr) return; + + // Translate each 32-bit code point range into the corresponding 16-bit code + // unit representation consisting of the lead- and trail surrogate. + // + // The generated alternatives are grouped by the leading surrogate to avoid + // emitting excessive code. For example, for + // + // { \ud800[\udc00-\udc01] + // , \ud800[\udc05-\udc06] + // } + // + // there's no need to emit matching code for the leading surrogate \ud800 + // twice. We also create a dedicated grouping for full trailing ranges, i.e. + // [dc00-dfff]. + ZoneUnorderedMap<UC16Range, ZoneList<CharacterRange>*> grouped_by_leading( + zone); + ZoneList<CharacterRange>* leading_with_full_trailing_range = + zone->New<ZoneList<CharacterRange>>(1, zone); + const auto AddRange = [&](base::uc16 from_l, base::uc16 to_l, + base::uc16 from_t, base::uc16 to_t) { + const UC16Range leading_range = ToUC16Range(from_l, to_l); + if (grouped_by_leading.count(leading_range) == 0) { + if (from_t == kTrailSurrogateStart && to_t == kTrailSurrogateEnd) { + leading_with_full_trailing_range->Add( + CharacterRange::Range(from_l, to_l), zone); + return; + } + grouped_by_leading[leading_range] = + zone->New<ZoneList<CharacterRange>>(2, zone); + } + grouped_by_leading[leading_range]->Add(CharacterRange::Range(from_t, to_t), + zone); + }; + + // First, create the grouped ranges. + CharacterRange::Canonicalize(non_bmp); + for (int i = 0; i < non_bmp->length(); i++) { + // Match surrogate pair. + // E.g. [\u10005-\u11005] becomes + // \ud800[\udc05-\udfff]| + // [\ud801-\ud803][\udc00-\udfff]| + // \ud804[\udc00-\udc05] + base::uc32 from = non_bmp->at(i).from(); + base::uc32 to = non_bmp->at(i).to(); + base::uc16 from_l = unibrow::Utf16::LeadSurrogate(from); + base::uc16 from_t = unibrow::Utf16::TrailSurrogate(from); + base::uc16 to_l = unibrow::Utf16::LeadSurrogate(to); + base::uc16 to_t = unibrow::Utf16::TrailSurrogate(to); + + if (from_l == to_l) { + // The lead surrogate is the same. + AddRange(from_l, to_l, from_t, to_t); + continue; + } + + if (from_t != kTrailSurrogateStart) { + // Add [from_l][from_t-\udfff]. + AddRange(from_l, from_l, from_t, kTrailSurrogateEnd); + from_l++; + } + if (to_t != kTrailSurrogateEnd) { + // Add [to_l][\udc00-to_t]. + AddRange(to_l, to_l, kTrailSurrogateStart, to_t); + to_l--; + } + if (from_l <= to_l) { + // Add [from_l-to_l][\udc00-\udfff]. + AddRange(from_l, to_l, kTrailSurrogateStart, kTrailSurrogateEnd); + } + } + + // Create the actual TextNode now that ranges are fully grouped. + if (!leading_with_full_trailing_range->is_empty()) { + CharacterRange::Canonicalize(leading_with_full_trailing_range); + result->AddAlternative(GuardedAlternative(TextNode::CreateForSurrogatePair( + zone, leading_with_full_trailing_range, + CharacterRange::Range(kTrailSurrogateStart, kTrailSurrogateEnd), + compiler->read_backward(), on_success))); + } + for (const auto& it : grouped_by_leading) { + CharacterRange leading_range = + CharacterRange::Range(ExtractFrom(it.first), ExtractTo(it.first)); + ZoneList<CharacterRange>* trailing_ranges = it.second; + CharacterRange::Canonicalize(trailing_ranges); + result->AddAlternative(GuardedAlternative(TextNode::CreateForSurrogatePair( + zone, leading_range, trailing_ranges, compiler->read_backward(), + on_success))); + } +} + +RegExpNode* NegativeLookaroundAgainstReadDirectionAndMatch( + RegExpCompiler* compiler, ZoneList<CharacterRange>* lookbehind, + ZoneList<CharacterRange>* match, RegExpNode* on_success, + bool read_backward) { + Zone* zone = compiler->zone(); + RegExpNode* match_node = TextNode::CreateForCharacterRanges( + zone, match, read_backward, on_success); + int stack_register = compiler->UnicodeLookaroundStackRegister(); + int position_register = compiler->UnicodeLookaroundPositionRegister(); + RegExpLookaround::Builder lookaround(false, match_node, stack_register, + position_register); + RegExpNode* negative_match = TextNode::CreateForCharacterRanges( + zone, lookbehind, !read_backward, lookaround.on_match_success()); + return lookaround.ForMatch(negative_match); +} + +RegExpNode* MatchAndNegativeLookaroundInReadDirection( + RegExpCompiler* compiler, ZoneList<CharacterRange>* match, + ZoneList<CharacterRange>* lookahead, RegExpNode* on_success, + bool read_backward) { + Zone* zone = compiler->zone(); + int stack_register = compiler->UnicodeLookaroundStackRegister(); + int position_register = compiler->UnicodeLookaroundPositionRegister(); + RegExpLookaround::Builder lookaround(false, on_success, stack_register, + position_register); + RegExpNode* negative_match = TextNode::CreateForCharacterRanges( + zone, lookahead, read_backward, lookaround.on_match_success()); + return TextNode::CreateForCharacterRanges( + zone, match, read_backward, lookaround.ForMatch(negative_match)); +} + +void AddLoneLeadSurrogates(RegExpCompiler* compiler, ChoiceNode* result, + RegExpNode* on_success, + UnicodeRangeSplitter* splitter) { + ZoneList<CharacterRange>* lead_surrogates = + ToCanonicalZoneList(splitter->lead_surrogates(), compiler->zone()); + if (lead_surrogates == nullptr) return; + Zone* zone = compiler->zone(); + // E.g. \ud801 becomes \ud801(?![\udc00-\udfff]). + ZoneList<CharacterRange>* trail_surrogates = CharacterRange::List( + zone, CharacterRange::Range(kTrailSurrogateStart, kTrailSurrogateEnd)); + + RegExpNode* match; + if (compiler->read_backward()) { + // Reading backward. Assert that reading forward, there is no trail + // surrogate, and then backward match the lead surrogate. + match = NegativeLookaroundAgainstReadDirectionAndMatch( + compiler, trail_surrogates, lead_surrogates, on_success, true); + } else { + // Reading forward. Forward match the lead surrogate and assert that + // no trail surrogate follows. + match = MatchAndNegativeLookaroundInReadDirection( + compiler, lead_surrogates, trail_surrogates, on_success, false); + } + result->AddAlternative(GuardedAlternative(match)); +} + +void AddLoneTrailSurrogates(RegExpCompiler* compiler, ChoiceNode* result, + RegExpNode* on_success, + UnicodeRangeSplitter* splitter) { + ZoneList<CharacterRange>* trail_surrogates = + ToCanonicalZoneList(splitter->trail_surrogates(), compiler->zone()); + if (trail_surrogates == nullptr) return; + Zone* zone = compiler->zone(); + // E.g. \udc01 becomes (?<![\ud800-\udbff])\udc01 + ZoneList<CharacterRange>* lead_surrogates = CharacterRange::List( + zone, CharacterRange::Range(kLeadSurrogateStart, kLeadSurrogateEnd)); + + RegExpNode* match; + if (compiler->read_backward()) { + // Reading backward. Backward match the trail surrogate and assert that no + // lead surrogate precedes it. + match = MatchAndNegativeLookaroundInReadDirection( + compiler, trail_surrogates, lead_surrogates, on_success, true); + } else { + // Reading forward. Assert that reading backward, there is no lead + // surrogate, and then forward match the trail surrogate. + match = NegativeLookaroundAgainstReadDirectionAndMatch( + compiler, lead_surrogates, trail_surrogates, on_success, false); + } + result->AddAlternative(GuardedAlternative(match)); +} + +RegExpNode* UnanchoredAdvance(RegExpCompiler* compiler, + RegExpNode* on_success) { + // This implements ES2015 21.2.5.2.3, AdvanceStringIndex. + DCHECK(!compiler->read_backward()); + Zone* zone = compiler->zone(); + // Advance any character. If the character happens to be a lead surrogate and + // we advanced into the middle of a surrogate pair, it will work out, as + // nothing will match from there. We will have to advance again, consuming + // the associated trail surrogate. + ZoneList<CharacterRange>* range = + CharacterRange::List(zone, CharacterRange::Range(0, kMaxUtf16CodeUnit)); + 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<CharacterRange>* ranges, + Zone* zone) { +#ifdef V8_INTL_SUPPORT + DCHECK(IsCanonical(ranges)); + + // Micro-optimization to avoid passing large ranges to UnicodeSet::closeOver. + // See also https://crbug.com/v8/6727. + // TODO(jgruber): This only covers the special case of the {0,0x10FFFF} range, + // which we use frequently internally. But large ranges can also easily be + // created by the user. We might want to have a more general caching mechanism + // for such ranges. + if (ranges->length() == 1 && ranges->at(0).IsEverything(kNonBmpEnd)) return; + + // Use ICU to compute the case fold closure over the ranges. + icu::UnicodeSet set; + 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); + 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); +#endif // V8_INTL_SUPPORT +} + +RegExpNode* RegExpClassRanges::ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) { + set_.Canonicalize(); + Zone* const zone = compiler->zone(); + ZoneList<CharacterRange>* ranges = this->ranges(zone); + + if (NeedsUnicodeCaseEquivalents(compiler->flags())) { + CharacterRange::AddUnicodeCaseEquivalents(ranges, zone); + } + + if (!IsEitherUnicode(compiler->flags()) || compiler->one_byte() || + contains_split_surrogate()) { + return zone->New<TextNode>(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 + // 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. + DCHECK_IMPLIES( + IsUnicodeSets(compiler->flags()), + ranges->length() == 1 && ranges->first().IsEverything(kMaxCodePoint)); + ZoneList<CharacterRange>* negated = + zone->New<ZoneList<CharacterRange>>(2, zone); + CharacterRange::Negate(ranges, negated, zone); + ranges = negated; + } + + if (ranges->length() == 0) { + // The empty character class is used as a 'fail' node. + RegExpClassRanges* fail = zone->New<RegExpClassRanges>(zone, ranges); + return zone->New<TextNode>(fail, compiler->read_backward(), on_success); + } + + if (set_.is_standard() && + standard_type() == StandardCharacterSet::kEverything) { + return UnanchoredAdvance(compiler, on_success); + } + + // Split ranges in order to handle surrogates correctly: + // - Surrogate pairs: translate the 32-bit code point into two uc16 code + // units (irregexp operates only on code units). + // - Lone surrogates: these require lookarounds to ensure we don't match in + // the middle of a surrogate pair. + ChoiceNode* result = zone->New<ChoiceNode>(2, zone); + UnicodeRangeSplitter splitter(ranges); + AddBmpCharacters(compiler, result, on_success, &splitter); + AddNonBmpSurrogatePairs(compiler, result, on_success, &splitter); + AddLoneLeadSurrogates(compiler, result, on_success, &splitter); + AddLoneTrailSurrogates(compiler, result, on_success, &splitter); + + static constexpr int kMaxRangesToInline = 32; // Arbitrary. + if (ranges->length() > kMaxRangesToInline) result->SetDoNotInline(); + + return result; +} + +RegExpNode* RegExpClassSetOperand::ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) { + Zone* zone = compiler->zone(); + const int size = (has_strings() ? static_cast<int>(strings()->size()) : 0) + + (ranges()->is_empty() ? 0 : 1); + if (size == 0) { + // If neither ranges nor strings are present, the operand is equal to an + // empty range (matching nothing). + ZoneList<CharacterRange>* empty = + zone->template New<ZoneList<CharacterRange>>(0, zone); + return zone->template New<RegExpClassRanges>(zone, empty) + ->ToNode(compiler, on_success); + } + ZoneList<RegExpTree*>* alternatives = + zone->template New<ZoneList<RegExpTree*>>(size, zone); + // Strings are sorted by length first (larger strings before shorter ones). + // See the comment on CharacterClassStrings. + // Empty strings (if present) are added after character ranges. + RegExpTree* empty_string = nullptr; + if (has_strings()) { + for (auto string : *strings()) { + if (string.second->IsEmpty()) { + empty_string = string.second; + } else { + alternatives->Add(string.second, zone); + } + } + } + if (!ranges()->is_empty()) { + alternatives->Add(zone->template New<RegExpClassRanges>(zone, ranges()), + zone); + } + if (empty_string != nullptr) { + alternatives->Add(empty_string, zone); + } + + RegExpTree* node = nullptr; + if (size == 1) { + DCHECK_EQ(alternatives->length(), 1); + node = alternatives->first(); + } else { + node = zone->template New<RegExpDisjunction>(alternatives); + } + return node->ToNode(compiler, on_success); +} + +RegExpNode* RegExpClassSetExpression::ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) { + Zone* zone = compiler->zone(); + ZoneList<CharacterRange>* temp_ranges = + zone->template New<ZoneList<CharacterRange>>(4, zone); + RegExpClassSetOperand* root = ComputeExpression(this, temp_ranges, zone); + return root->ToNode(compiler, on_success); +} + +void RegExpClassSetOperand::Union(RegExpClassSetOperand* other, Zone* zone) { + ranges()->AddAll(*other->ranges(), zone); + if (other->has_strings()) { + if (strings_ == nullptr) { + strings_ = zone->template New<CharacterClassStrings>(zone); + } + strings()->insert(other->strings()->begin(), other->strings()->end()); + } +} + +void RegExpClassSetOperand::Intersect(RegExpClassSetOperand* other, + ZoneList<CharacterRange>* temp_ranges, + Zone* zone) { + CharacterRange::Intersect(ranges(), other->ranges(), temp_ranges, zone); + std::swap(*ranges(), *temp_ranges); + temp_ranges->Rewind(0); + if (has_strings()) { + if (!other->has_strings()) { + strings()->clear(); + } else { + for (auto iter = strings()->begin(); iter != strings()->end();) { + if (other->strings()->find(iter->first) == other->strings()->end()) { + iter = strings()->erase(iter); + } else { + iter++; + } + } + } + } +} + +void RegExpClassSetOperand::Subtract(RegExpClassSetOperand* other, + ZoneList<CharacterRange>* temp_ranges, + Zone* zone) { + CharacterRange::Subtract(ranges(), other->ranges(), temp_ranges, zone); + std::swap(*ranges(), *temp_ranges); + temp_ranges->Rewind(0); + if (has_strings() && other->has_strings()) { + for (auto iter = strings()->begin(); iter != strings()->end();) { + if (other->strings()->find(iter->first) != other->strings()->end()) { + iter = strings()->erase(iter); + } else { + iter++; + } + } + } +} + +// static +RegExpClassSetOperand* RegExpClassSetExpression::ComputeExpression( + RegExpTree* root, ZoneList<CharacterRange>* temp_ranges, Zone* zone) { + DCHECK(temp_ranges->is_empty()); + if (root->IsClassSetOperand()) { + return root->AsClassSetOperand(); + } + DCHECK(root->IsClassSetExpression()); + RegExpClassSetExpression* node = root->AsClassSetExpression(); + RegExpClassSetOperand* result = + ComputeExpression(node->operands()->at(0), temp_ranges, zone); + switch (node->operation()) { + case OperationType::kUnion: { + for (int i = 1; i < node->operands()->length(); i++) { + RegExpClassSetOperand* op = + ComputeExpression(node->operands()->at(i), temp_ranges, zone); + result->Union(op, zone); + } + CharacterRange::Canonicalize(result->ranges()); + break; + } + case OperationType::kIntersection: { + for (int i = 1; i < node->operands()->length(); i++) { + RegExpClassSetOperand* op = + ComputeExpression(node->operands()->at(i), temp_ranges, zone); + result->Intersect(op, temp_ranges, zone); + } + break; + } + case OperationType::kSubtraction: { + for (int i = 1; i < node->operands()->length(); i++) { + RegExpClassSetOperand* op = + ComputeExpression(node->operands()->at(i), temp_ranges, zone); + result->Subtract(op, temp_ranges, zone); + } + break; + } + } + if (node->is_negated()) { + DCHECK(!result->has_strings()); + CharacterRange::Negate(result->ranges(), temp_ranges, zone); + std::swap(*result->ranges(), *temp_ranges); + temp_ranges->Rewind(0); + node->is_negated_ = false; + } + // Store the result as single operand of the current node. + node->operands()->Set(0, result); + node->operands()->Rewind(1); + + return result; +} + +namespace { + +int CompareFirstChar(RegExpTree* const* a, RegExpTree* const* b) { + RegExpAtom* atom1 = (*a)->AsAtom(); + RegExpAtom* atom2 = (*b)->AsAtom(); + base::uc16 character1 = atom1->data().at(0); + base::uc16 character2 = atom2->data().at(0); + if (character1 < character2) return -1; + if (character1 > character2) return 1; + return 0; +} + +#ifdef V8_INTL_SUPPORT + +int CompareCaseInsensitive(const icu::UnicodeString& a, + const icu::UnicodeString& b) { + return a.caseCompare(b, U_FOLD_CASE_DEFAULT); +} + +int CompareFirstCharCaseInsensitive(RegExpTree* const* a, + RegExpTree* const* b) { + RegExpAtom* atom1 = (*a)->AsAtom(); + RegExpAtom* atom2 = (*b)->AsAtom(); + return CompareCaseInsensitive(icu::UnicodeString{atom1->data().at(0)}, + icu::UnicodeString{atom2->data().at(0)}); +} + +bool Equals(bool ignore_case, const icu::UnicodeString& a, + const icu::UnicodeString& b) { + if (a == b) return true; + if (ignore_case) return CompareCaseInsensitive(a, b) == 0; + return false; // Case-sensitive equality already checked above. +} + +bool CharAtEquals(bool ignore_case, int index, const RegExpAtom* a, + const RegExpAtom* b) { + return Equals(ignore_case, a->data().at(index), b->data().at(index)); +} + +#else + +unibrow::uchar Canonical( + unibrow::Mapping<unibrow::Ecma262Canonicalize>* canonicalize, + unibrow::uchar c) { + unibrow::uchar chars[unibrow::Ecma262Canonicalize::kMaxWidth]; + int length = canonicalize->get(c, '\0', chars); + DCHECK_LE(length, 1); + unibrow::uchar canonical = c; + if (length == 1) canonical = chars[0]; + return canonical; +} + +int CompareCaseInsensitive( + unibrow::Mapping<unibrow::Ecma262Canonicalize>* canonicalize, + unibrow::uchar a, unibrow::uchar b) { + if (a == b) return 0; + if (a >= 'a' || b >= 'a') { + a = Canonical(canonicalize, a); + b = Canonical(canonicalize, b); + } + return static_cast<int>(a) - static_cast<int>(b); +} + +int CompareFirstCharCaseInsensitive( + unibrow::Mapping<unibrow::Ecma262Canonicalize>* canonicalize, + RegExpTree* const* a, RegExpTree* const* b) { + RegExpAtom* atom1 = (*a)->AsAtom(); + RegExpAtom* atom2 = (*b)->AsAtom(); + return CompareCaseInsensitive(canonicalize, atom1->data().at(0), + atom2->data().at(0)); +} + +bool Equals(bool ignore_case, + unibrow::Mapping<unibrow::Ecma262Canonicalize>* canonicalize, + unibrow::uchar a, unibrow::uchar b) { + if (a == b) return true; + if (ignore_case) { + return CompareCaseInsensitive(canonicalize, a, b) == 0; + } + return false; // Case-sensitive equality already checked above. +} + +bool CharAtEquals(bool ignore_case, + unibrow::Mapping<unibrow::Ecma262Canonicalize>* canonicalize, + int index, const RegExpAtom* a, const RegExpAtom* b) { + return Equals(ignore_case, canonicalize, a->data().at(index), + b->data().at(index)); +} + +#endif // V8_INTL_SUPPORT + +} // namespace + +// We can stable sort runs of atoms, since the order does not matter if they +// start with different characters. +// Returns true if any consecutive atoms were found. +bool RegExpDisjunction::SortConsecutiveAtoms(RegExpCompiler* compiler) { + ZoneList<RegExpTree*>* alternatives = this->alternatives(); + int length = alternatives->length(); + bool found_consecutive_atoms = false; + for (int i = 0; i < length; i++) { + while (i < length) { + RegExpTree* alternative = alternatives->at(i); + if (alternative->IsAtom()) break; + i++; + } + // i is length or it is the index of an atom. + if (i == length) break; + int first_atom = i; + i++; + while (i < length) { + RegExpTree* alternative = alternatives->at(i); + if (!alternative->IsAtom()) break; + i++; + } + // Sort atoms to get ones with common prefixes together. + // This step is more tricky if we are in a case-independent regexp, + // because it would change /is|I/ to /I|is/, and order matters when + // the regexp parts don't match only disjoint starting points. To fix + // this we have a version of CompareFirstChar that uses case- + // independent character classes for comparison. + DCHECK_LT(first_atom, alternatives->length()); + DCHECK_LE(i, alternatives->length()); + DCHECK_LE(first_atom, i); + if (IsIgnoreCase(compiler->flags())) { +#ifdef V8_INTL_SUPPORT + alternatives->StableSort(CompareFirstCharCaseInsensitive, first_atom, + i - first_atom); +#else + unibrow::Mapping<unibrow::Ecma262Canonicalize>* canonicalize = + compiler->isolate()->regexp_macro_assembler_canonicalize(); + auto compare_closure = [canonicalize](RegExpTree* const* a, + RegExpTree* const* b) { + return CompareFirstCharCaseInsensitive(canonicalize, a, b); + }; + alternatives->StableSort(compare_closure, first_atom, i - first_atom); +#endif // V8_INTL_SUPPORT + } else { + alternatives->StableSort(CompareFirstChar, first_atom, i - first_atom); + } + if (i - first_atom > 1) found_consecutive_atoms = true; + } + return found_consecutive_atoms; +} + +// Optimizes ab|ac|az to a(?:b|c|d). +void RegExpDisjunction::RationalizeConsecutiveAtoms(RegExpCompiler* compiler) { + Zone* zone = compiler->zone(); + ZoneList<RegExpTree*>* alternatives = this->alternatives(); + int length = alternatives->length(); + const bool ignore_case = IsIgnoreCase(compiler->flags()); + + int write_posn = 0; + int i = 0; + while (i < length) { + RegExpTree* alternative = alternatives->at(i); + if (!alternative->IsAtom()) { + alternatives->at(write_posn++) = alternatives->at(i); + i++; + continue; + } + RegExpAtom* const atom = alternative->AsAtom(); +#ifdef V8_INTL_SUPPORT + icu::UnicodeString common_prefix(atom->data().at(0)); +#else + unibrow::Mapping<unibrow::Ecma262Canonicalize>* const canonicalize = + compiler->isolate()->regexp_macro_assembler_canonicalize(); + unibrow::uchar common_prefix = atom->data().at(0); + if (ignore_case) { + common_prefix = Canonical(canonicalize, common_prefix); + } +#endif // V8_INTL_SUPPORT + int first_with_prefix = i; + int prefix_length = atom->length(); + i++; + while (i < length) { + alternative = alternatives->at(i); + if (!alternative->IsAtom()) break; + RegExpAtom* const alt_atom = alternative->AsAtom(); +#ifdef V8_INTL_SUPPORT + icu::UnicodeString new_prefix(alt_atom->data().at(0)); + if (!Equals(ignore_case, new_prefix, common_prefix)) break; +#else + unibrow::uchar new_prefix = alt_atom->data().at(0); + if (!Equals(ignore_case, canonicalize, new_prefix, common_prefix)) break; +#endif // V8_INTL_SUPPORT + prefix_length = std::min(prefix_length, alt_atom->length()); + i++; + } + if (i > first_with_prefix + 2) { + // Found worthwhile run of alternatives with common prefix of at least one + // character. The sorting function above did not sort on more than one + // character for reasons of correctness, but there may still be a longer + // common prefix if the terms were similar or presorted in the input. + // Find out how long the common prefix is. + int run_length = i - first_with_prefix; + RegExpAtom* const alt_atom = + alternatives->at(first_with_prefix)->AsAtom(); + for (int j = 1; j < run_length && prefix_length > 1; j++) { + RegExpAtom* old_atom = + alternatives->at(j + first_with_prefix)->AsAtom(); + for (int k = 1; k < prefix_length; k++) { +#ifdef V8_INTL_SUPPORT + if (!CharAtEquals(ignore_case, k, alt_atom, old_atom)) { +#else + if (!CharAtEquals(ignore_case, canonicalize, k, alt_atom, old_atom)) { +#endif // V8_INTL_SUPPORT + prefix_length = k; + break; + } + } + } + RegExpAtom* prefix = + zone->New<RegExpAtom>(alt_atom->data().SubVector(0, prefix_length)); + ZoneList<RegExpTree*>* pair = zone->New<ZoneList<RegExpTree*>>(2, zone); + pair->Add(prefix, zone); + ZoneList<RegExpTree*>* suffixes = + zone->New<ZoneList<RegExpTree*>>(run_length, zone); + for (int j = 0; j < run_length; j++) { + RegExpAtom* old_atom = + alternatives->at(j + first_with_prefix)->AsAtom(); + int len = old_atom->length(); + if (len == prefix_length) { + suffixes->Add(zone->New<RegExpEmpty>(), zone); + } else { + RegExpTree* suffix = zone->New<RegExpAtom>( + old_atom->data().SubVector(prefix_length, old_atom->length())); + suffixes->Add(suffix, zone); + } + } + pair->Add(zone->New<RegExpDisjunction>(suffixes), zone); + alternatives->at(write_posn++) = zone->New<RegExpAlternative>(pair); + } else { + // Just copy any non-worthwhile alternatives. + for (int j = first_with_prefix; j < i; j++) { + alternatives->at(write_posn++) = alternatives->at(j); + } + } + } + alternatives->Rewind(write_posn); // Trim end of array. +} + +// Optimizes b|c|z to [bcz]. +void RegExpDisjunction::FixSingleCharacterDisjunctions( + RegExpCompiler* compiler) { + Zone* zone = compiler->zone(); + ZoneList<RegExpTree*>* alternatives = this->alternatives(); + int length = alternatives->length(); + + int write_posn = 0; + int i = 0; + while (i < length) { + RegExpTree* alternative = alternatives->at(i); + if (!alternative->IsAtom()) { + alternatives->at(write_posn++) = alternatives->at(i); + i++; + continue; + } + RegExpAtom* const atom = alternative->AsAtom(); + if (atom->length() != 1) { + alternatives->at(write_posn++) = alternatives->at(i); + i++; + continue; + } + const RegExpFlags flags = compiler->flags(); + DCHECK_IMPLIES(IsEitherUnicode(flags), + !unibrow::Utf16::IsLeadSurrogate(atom->data().at(0))); + bool contains_trail_surrogate = + unibrow::Utf16::IsTrailSurrogate(atom->data().at(0)); + int first_in_run = i; + i++; + // Find a run of single-character atom alternatives that have identical + // flags (case independence and unicode-ness). + while (i < length) { + alternative = alternatives->at(i); + if (!alternative->IsAtom()) break; + RegExpAtom* const alt_atom = alternative->AsAtom(); + if (alt_atom->length() != 1) break; + DCHECK_IMPLIES(IsEitherUnicode(flags), + !unibrow::Utf16::IsLeadSurrogate(alt_atom->data().at(0))); + contains_trail_surrogate |= + unibrow::Utf16::IsTrailSurrogate(alt_atom->data().at(0)); + i++; + } + if (i > first_in_run + 1) { + // Found non-trivial run of single-character alternatives. + int run_length = i - first_in_run; + ZoneList<CharacterRange>* ranges = + zone->New<ZoneList<CharacterRange>>(2, zone); + for (int j = 0; j < run_length; j++) { + RegExpAtom* old_atom = alternatives->at(j + first_in_run)->AsAtom(); + DCHECK_EQ(old_atom->length(), 1); + ranges->Add(CharacterRange::Singleton(old_atom->data().at(0)), zone); + } + RegExpClassRanges::ClassRangesFlags class_ranges_flags; + if (IsEitherUnicode(flags) && contains_trail_surrogate) { + class_ranges_flags = RegExpClassRanges::CONTAINS_SPLIT_SURROGATE; + } + alternatives->at(write_posn++) = + zone->New<RegExpClassRanges>(zone, ranges, class_ranges_flags); + } else { + // Just copy any trivial alternatives. + for (int j = first_in_run; j < i; j++) { + alternatives->at(write_posn++) = alternatives->at(j); + } + } + } + alternatives->Rewind(write_posn); // Trim end of array. +} + +RegExpNode* RegExpDisjunction::ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) { + compiler->ToNodeMaybeCheckForStackOverflow(); + + ZoneList<RegExpTree*>* alternatives = this->alternatives(); + + if (alternatives->length() > 2) { + bool found_consecutive_atoms = SortConsecutiveAtoms(compiler); + if (found_consecutive_atoms) RationalizeConsecutiveAtoms(compiler); + FixSingleCharacterDisjunctions(compiler); + if (alternatives->length() == 1) { + return alternatives->at(0)->ToNode(compiler, on_success); + } + } + + int length = alternatives->length(); + + ChoiceNode* result = + compiler->zone()->New<ChoiceNode>(length, compiler->zone()); + for (int i = 0; i < length; i++) { + GuardedAlternative alternative( + alternatives->at(i)->ToNode(compiler, on_success)); + result->AddAlternative(alternative); + } + return result; +} + +RegExpNode* RegExpQuantifier::ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) { + return ToNode(min(), max(), is_greedy(), body(), compiler, on_success); +} + +namespace { +// Desugar \b to (?<=\w)(?=\W)|(?<=\W)(?=\w) and +// \B to (?<=\w)(?=\w)|(?<=\W)(?=\W) +RegExpNode* BoundaryAssertionAsLookaround(RegExpCompiler* compiler, + RegExpNode* on_success, + RegExpAssertion::Type type, + RegExpFlags flags) { + CHECK(NeedsUnicodeCaseEquivalents(flags)); + Zone* zone = compiler->zone(); + ZoneList<CharacterRange>* word_range = + zone->New<ZoneList<CharacterRange>>(2, zone); + CharacterRange::AddClassEscape(StandardCharacterSet::kWord, word_range, true, + zone); + int stack_register = compiler->UnicodeLookaroundStackRegister(); + int position_register = compiler->UnicodeLookaroundPositionRegister(); + ChoiceNode* result = zone->New<ChoiceNode>(2, zone); + // Add two choices. The (non-)boundary could start with a word or + // a non-word-character. + for (int i = 0; i < 2; i++) { + bool lookbehind_for_word = i == 0; + bool lookahead_for_word = + (type == RegExpAssertion::Type::BOUNDARY) ^ lookbehind_for_word; + // Look to the left. + RegExpLookaround::Builder lookbehind(lookbehind_for_word, on_success, + stack_register, position_register); + RegExpNode* backward = TextNode::CreateForCharacterRanges( + zone, word_range, true, lookbehind.on_match_success()); + // Look to the right. + RegExpLookaround::Builder lookahead(lookahead_for_word, + lookbehind.ForMatch(backward), + stack_register, position_register); + RegExpNode* forward = TextNode::CreateForCharacterRanges( + zone, word_range, false, lookahead.on_match_success()); + result->AddAlternative(GuardedAlternative(lookahead.ForMatch(forward))); + } + return result; +} +} // anonymous namespace + +RegExpNode* RegExpAssertion::ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) { + NodeInfo info; + Zone* zone = compiler->zone(); + + switch (assertion_type()) { + case Type::START_OF_LINE: + return AssertionNode::AfterNewline(on_success); + case Type::START_OF_INPUT: + return AssertionNode::AtStart(on_success); + case Type::BOUNDARY: + return NeedsUnicodeCaseEquivalents(compiler->flags()) + ? BoundaryAssertionAsLookaround( + compiler, on_success, Type::BOUNDARY, compiler->flags()) + : AssertionNode::AtBoundary(on_success); + case Type::NON_BOUNDARY: + return NeedsUnicodeCaseEquivalents(compiler->flags()) + ? BoundaryAssertionAsLookaround(compiler, on_success, + Type::NON_BOUNDARY, + compiler->flags()) + : AssertionNode::AtNonBoundary(on_success); + case Type::END_OF_INPUT: + return AssertionNode::AtEnd(on_success); + case Type::END_OF_LINE: { + // Compile $ in multiline regexps as an alternation with a positive + // lookahead in one side and an end-of-input on the other side. + // We need two registers for the lookahead. + int stack_pointer_register = compiler->AllocateRegister(); + int position_register = compiler->AllocateRegister(); + // The ChoiceNode to distinguish between a newline and end-of-input. + ChoiceNode* result = zone->New<ChoiceNode>(2, zone); + // Create a newline atom. + ZoneList<CharacterRange>* newline_ranges = + zone->New<ZoneList<CharacterRange>>(3, zone); + CharacterRange::AddClassEscape(StandardCharacterSet::kLineTerminator, + newline_ranges, false, zone); + RegExpClassRanges* newline_atom = + zone->New<RegExpClassRanges>(StandardCharacterSet::kLineTerminator); + TextNode* newline_matcher = + zone->New<TextNode>(newline_atom, false, + ActionNode::PositiveSubmatchSuccess( + stack_pointer_register, position_register, + 0, // No captures inside. + -1, // Ignored if no captures. + on_success)); + // Create an end-of-input matcher. + RegExpNode* end_of_line = ActionNode::BeginPositiveSubmatch( + stack_pointer_register, position_register, newline_matcher); + // Add the two alternatives to the ChoiceNode. + GuardedAlternative eol_alternative(end_of_line); + result->AddAlternative(eol_alternative); + GuardedAlternative end_alternative(AssertionNode::AtEnd(on_success)); + result->AddAlternative(end_alternative); + return result; + } + default: + UNREACHABLE(); + } +} + +RegExpNode* RegExpBackReference::ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) { + return compiler->zone()->New<BackReferenceNode>( + RegExpCapture::StartRegister(index()), + RegExpCapture::EndRegister(index()), flags_, compiler->read_backward(), + on_success); +} + +RegExpNode* RegExpEmpty::ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) { + return on_success; +} + +RegExpNode* RegExpGroup::ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) { + return body_->ToNode(compiler, on_success); +} + +RegExpLookaround::Builder::Builder(bool is_positive, RegExpNode* on_success, + int stack_pointer_register, + int position_register, + int capture_register_count, + int capture_register_start) + : is_positive_(is_positive), + on_success_(on_success), + stack_pointer_register_(stack_pointer_register), + position_register_(position_register) { + if (is_positive_) { + on_match_success_ = ActionNode::PositiveSubmatchSuccess( + stack_pointer_register, position_register, capture_register_count, + capture_register_start, on_success_); + } else { + Zone* zone = on_success_->zone(); + on_match_success_ = zone->New<NegativeSubmatchSuccess>( + stack_pointer_register, position_register, capture_register_count, + capture_register_start, zone); + } +} + +RegExpNode* RegExpLookaround::Builder::ForMatch(RegExpNode* match) { + if (is_positive_) { + return ActionNode::BeginPositiveSubmatch(stack_pointer_register_, + position_register_, match); + } else { + Zone* zone = on_success_->zone(); + // We use a ChoiceNode to represent the negative lookaround. The first + // alternative is the negative match. On success, the end node backtracks. + // On failure, the second alternative is tried and leads to success. + // NegativeLookaheadChoiceNode is a special ChoiceNode that ignores the + // first exit when calculating quick checks. + ChoiceNode* choice_node = zone->New<NegativeLookaroundChoiceNode>( + GuardedAlternative(match), GuardedAlternative(on_success_), zone); + return ActionNode::BeginNegativeSubmatch(stack_pointer_register_, + position_register_, choice_node); + } +} + +RegExpNode* RegExpLookaround::ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) { + int stack_pointer_register = compiler->AllocateRegister(); + int position_register = compiler->AllocateRegister(); + + const int registers_per_capture = 2; + const int register_of_first_capture = 2; + int register_count = capture_count_ * registers_per_capture; + int register_start = + register_of_first_capture + capture_from_ * registers_per_capture; + + RegExpNode* result; + bool was_reading_backward = compiler->read_backward(); + compiler->set_read_backward(type() == LOOKBEHIND); + Builder builder(is_positive(), on_success, stack_pointer_register, + position_register, register_count, register_start); + RegExpNode* match = body_->ToNode(compiler, builder.on_match_success()); + result = builder.ForMatch(match); + compiler->set_read_backward(was_reading_backward); + return result; +} + +RegExpNode* RegExpCapture::ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) { + return ToNode(body(), index(), compiler, on_success); +} + +RegExpNode* RegExpCapture::ToNode(RegExpTree* body, int index, + RegExpCompiler* compiler, + RegExpNode* on_success) { + DCHECK_NOT_NULL(body); + int start_reg = RegExpCapture::StartRegister(index); + int end_reg = RegExpCapture::EndRegister(index); + if (compiler->read_backward()) std::swap(start_reg, end_reg); + RegExpNode* store_end = ActionNode::StorePosition(end_reg, true, on_success); + RegExpNode* body_node = body->ToNode(compiler, store_end); + return ActionNode::StorePosition(start_reg, true, body_node); +} + +namespace { + +class AssertionSequenceRewriter final { + public: + // TODO(jgruber): Consider moving this to a separate AST tree rewriter pass + // instead of sprinkling rewrites into the AST->Node conversion process. + static void MaybeRewrite(ZoneList<RegExpTree*>* terms, Zone* zone) { + AssertionSequenceRewriter rewriter(terms, zone); + + static constexpr int kNoIndex = -1; + int from = kNoIndex; + + for (int i = 0; i < terms->length(); i++) { + RegExpTree* t = terms->at(i); + if (from == kNoIndex && t->IsAssertion()) { + from = i; // Start a sequence. + } else if (from != kNoIndex && !t->IsAssertion()) { + // Terminate and process the sequence. + if (i - from > 1) rewriter.Rewrite(from, i); + from = kNoIndex; + } + } + + if (from != kNoIndex && terms->length() - from > 1) { + rewriter.Rewrite(from, terms->length()); + } + } + + // All assertions are zero width. A consecutive sequence of assertions is + // order-independent. There's two ways we can optimize here: + // 1. fold all identical assertions. + // 2. if any assertion combinations are known to fail (e.g. \b\B), the entire + // sequence fails. + void Rewrite(int from, int to) { + DCHECK_GT(to, from + 1); + + // Bitfield of all seen assertions. + uint32_t seen_assertions = 0; + static_assert(static_cast<int>(RegExpAssertion::Type::LAST_ASSERTION_TYPE) < + kUInt32Size * kBitsPerByte); + + for (int i = from; i < to; i++) { + RegExpAssertion* t = terms_->at(i)->AsAssertion(); + const uint32_t bit = 1 << static_cast<int>(t->assertion_type()); + + if (seen_assertions & bit) { + // Fold duplicates. + terms_->Set(i, zone_->New<RegExpEmpty>()); + } + + seen_assertions |= bit; + } + + // Collapse failures. + const uint32_t always_fails_mask = + 1 << static_cast<int>(RegExpAssertion::Type::BOUNDARY) | + 1 << static_cast<int>(RegExpAssertion::Type::NON_BOUNDARY); + if ((seen_assertions & always_fails_mask) == always_fails_mask) { + ReplaceSequenceWithFailure(from, to); + } + } + + void ReplaceSequenceWithFailure(int from, int to) { + // Replace the entire sequence with a single node that always fails. + // TODO(jgruber): Consider adding an explicit Fail kind. Until then, the + // negated '*' (everything) range serves the purpose. + ZoneList<CharacterRange>* ranges = + zone_->New<ZoneList<CharacterRange>>(0, zone_); + RegExpClassRanges* cc = zone_->New<RegExpClassRanges>(zone_, ranges); + terms_->Set(from, cc); + + // Zero out the rest. + RegExpEmpty* empty = zone_->New<RegExpEmpty>(); + for (int i = from + 1; i < to; i++) terms_->Set(i, empty); + } + + private: + AssertionSequenceRewriter(ZoneList<RegExpTree*>* terms, Zone* zone) + : zone_(zone), terms_(terms) {} + + Zone* zone_; + ZoneList<RegExpTree*>* terms_; +}; + +} // namespace + +RegExpNode* RegExpAlternative::ToNode(RegExpCompiler* compiler, + RegExpNode* on_success) { + compiler->ToNodeMaybeCheckForStackOverflow(); + + ZoneList<RegExpTree*>* children = nodes(); + + AssertionSequenceRewriter::MaybeRewrite(children, compiler->zone()); + + RegExpNode* current = on_success; + if (compiler->read_backward()) { + for (int i = 0; i < children->length(); i++) { + current = children->at(i)->ToNode(compiler, current); + } + } else { + for (int i = children->length() - 1; i >= 0; i--) { + current = children->at(i)->ToNode(compiler, current); + } + } + return current; +} + +namespace { + +void AddClass(const int* elmv, int elmc, ZoneList<CharacterRange>* ranges, + Zone* zone) { + elmc--; + DCHECK_EQ(kRangeEndMarker, elmv[elmc]); + for (int i = 0; i < elmc; i += 2) { + DCHECK(elmv[i] < elmv[i + 1]); + ranges->Add(CharacterRange::Range(elmv[i], elmv[i + 1] - 1), zone); + } +} + +void AddClassNegated(const int* elmv, int elmc, + ZoneList<CharacterRange>* ranges, Zone* zone) { + elmc--; + DCHECK_EQ(kRangeEndMarker, elmv[elmc]); + DCHECK_NE(0x0000, elmv[0]); + DCHECK_NE(kMaxCodePoint, elmv[elmc - 1]); + base::uc16 last = 0x0000; + for (int i = 0; i < elmc; i += 2) { + DCHECK(last <= elmv[i] - 1); + DCHECK(elmv[i] < elmv[i + 1]); + ranges->Add(CharacterRange::Range(last, elmv[i] - 1), zone); + last = elmv[i + 1]; + } + ranges->Add(CharacterRange::Range(last, kMaxCodePoint), zone); +} + +} // namespace + +void CharacterRange::AddClassEscape(StandardCharacterSet standard_character_set, + ZoneList<CharacterRange>* ranges, + bool add_unicode_case_equivalents, + Zone* zone) { + if (add_unicode_case_equivalents && + (standard_character_set == StandardCharacterSet::kWord || + standard_character_set == StandardCharacterSet::kNotWord)) { + // See #sec-runtime-semantics-wordcharacters-abstract-operation + // In case of unicode and ignore_case, we need to create the closure over + // case equivalent characters before negating. + ZoneList<CharacterRange>* new_ranges = + zone->New<ZoneList<CharacterRange>>(2, zone); + AddClass(kWordRanges, kWordRangeCount, new_ranges, zone); + AddUnicodeCaseEquivalents(new_ranges, zone); + if (standard_character_set == StandardCharacterSet::kNotWord) { + ZoneList<CharacterRange>* negated = + zone->New<ZoneList<CharacterRange>>(2, zone); + CharacterRange::Negate(new_ranges, negated, zone); + new_ranges = negated; + } + ranges->AddAll(*new_ranges, zone); + return; + } + + switch (standard_character_set) { + case StandardCharacterSet::kWhitespace: + AddClass(kSpaceRanges, kSpaceRangeCount, ranges, zone); + break; + case StandardCharacterSet::kNotWhitespace: + AddClassNegated(kSpaceRanges, kSpaceRangeCount, ranges, zone); + break; + case StandardCharacterSet::kWord: + AddClass(kWordRanges, kWordRangeCount, ranges, zone); + break; + case StandardCharacterSet::kNotWord: + AddClassNegated(kWordRanges, kWordRangeCount, ranges, zone); + break; + case StandardCharacterSet::kDigit: + AddClass(kDigitRanges, kDigitRangeCount, ranges, zone); + break; + case StandardCharacterSet::kNotDigit: + AddClassNegated(kDigitRanges, kDigitRangeCount, ranges, zone); + break; + // This is the set of characters matched by the $ and ^ symbols + // in multiline mode. + case StandardCharacterSet::kLineTerminator: + AddClass(kLineTerminatorRanges, kLineTerminatorRangeCount, ranges, zone); + break; + case StandardCharacterSet::kNotLineTerminator: + AddClassNegated(kLineTerminatorRanges, kLineTerminatorRangeCount, ranges, + zone); + break; + // This is not a character range as defined by the spec but a + // convenient shorthand for a character class that matches any + // character. + case StandardCharacterSet::kEverything: + ranges->Add(CharacterRange::Everything(), zone); + break; + } +} + +// static +void CharacterRange::AddCaseEquivalents(Isolate* isolate, Zone* zone, + ZoneList<CharacterRange>* ranges, + bool is_one_byte) { + CharacterRange::Canonicalize(ranges); + int range_count = ranges->length(); +#ifdef V8_INTL_SUPPORT + icu::UnicodeSet others; + for (int i = 0; i < range_count; i++) { + CharacterRange range = ranges->at(i); + base::uc32 from = range.from(); + if (from > kMaxUtf16CodeUnit) continue; + base::uc32 to = std::min({range.to(), kMaxUtf16CodeUnitU}); + // Nothing to be done for surrogates. + if (from >= kLeadSurrogateStart && to <= kTrailSurrogateEnd) continue; + if (is_one_byte && !RangeContainsLatin1Equivalents(range)) { + if (from > kMaxOneByteCharCode) continue; + if (to > kMaxOneByteCharCode) to = kMaxOneByteCharCode; + } + others.add(from, to); + } + + // Compute the set of additional characters that should be added, + // using UnicodeSet::closeOver. ECMA 262 defines slightly different + // case-folding rules than Unicode, so some characters that are + // added by closeOver do not match anything other than themselves in + // JS. For example, 'Å¿' (U+017F LATIN SMALL LETTER LONG S) is the + // same case-insensitive character as 's' or 'S' according to + // Unicode, but does not match any other character in JS. To handle + // this case, we add such characters to the IgnoreSet and filter + // them out. We filter twice: once before calling closeOver (to + // prevent 'Å¿' from adding 's'), and once after calling closeOver + // (to prevent 's' from adding 'Å¿'). See regexp/special-case.h for + // more information. + icu::UnicodeSet already_added(others); + others.removeAll(RegExpCaseFolding::IgnoreSet()); + others.closeOver(USET_CASE_INSENSITIVE); + others.removeAll(RegExpCaseFolding::IgnoreSet()); + others.removeAll(already_added); + + // Add others to the ranges + for (int32_t i = 0; i < others.getRangeCount(); i++) { + UChar32 from = others.getRangeStart(i); + UChar32 to = others.getRangeEnd(i); + if (from == to) { + ranges->Add(CharacterRange::Singleton(from), zone); + } else { + ranges->Add(CharacterRange::Range(from, to), zone); + } + } +#else + for (int i = 0; i < range_count; i++) { + CharacterRange range = ranges->at(i); + base::uc32 bottom = range.from(); + if (bottom > kMaxUtf16CodeUnit) continue; + base::uc32 top = std::min({range.to(), kMaxUtf16CodeUnitU}); + // Nothing to be done for surrogates. + if (bottom >= kLeadSurrogateStart && top <= kTrailSurrogateEnd) continue; + if (is_one_byte && !RangeContainsLatin1Equivalents(range)) { + if (bottom > kMaxOneByteCharCode) continue; + if (top > kMaxOneByteCharCode) top = kMaxOneByteCharCode; + } + unibrow::uchar chars[unibrow::Ecma262UnCanonicalize::kMaxWidth]; + if (top == bottom) { + // If this is a singleton we just expand the one character. + int length = isolate->jsregexp_uncanonicalize()->get(bottom, '\0', chars); + for (int i = 0; i < length; i++) { + base::uc32 chr = chars[i]; + if (chr != bottom) { + ranges->Add(CharacterRange::Singleton(chars[i]), zone); + } + } + } else { + // If this is a range we expand the characters block by block, expanding + // contiguous subranges (blocks) one at a time. The approach is as + // follows. For a given start character we look up the remainder of the + // block that contains it (represented by the end point), for instance we + // find 'z' if the character is 'c'. A block is characterized by the + // property that all characters uncanonicalize in the same way, except + // that each entry in the result is incremented by the distance from the + // first element. So a-z is a block because 'a' uncanonicalizes to ['a', + // 'A'] and the k'th letter uncanonicalizes to ['a' + k, 'A' + k]. Once + // we've found the end point we look up its uncanonicalization and + // produce a range for each element. For instance for [c-f] we look up + // ['z', 'Z'] and produce [c-f] and [C-F]. We then only add a range if + // it is not already contained in the input, so [c-f] will be skipped but + // [C-F] will be added. If this range is not completely contained in a + // block we do this for all the blocks covered by the range (handling + // characters that is not in a block as a "singleton block"). + unibrow::uchar equivalents[unibrow::Ecma262UnCanonicalize::kMaxWidth]; + base::uc32 pos = bottom; + while (pos <= top) { + int length = + isolate->jsregexp_canonrange()->get(pos, '\0', equivalents); + base::uc32 block_end; + if (length == 0) { + block_end = pos; + } else { + DCHECK_EQ(1, length); + block_end = equivalents[0]; + } + int end = (block_end > top) ? top : block_end; + length = isolate->jsregexp_uncanonicalize()->get(block_end, '\0', + equivalents); + for (int i = 0; i < length; i++) { + base::uc32 c = equivalents[i]; + base::uc32 range_from = c - (block_end - pos); + base::uc32 range_to = c - (block_end - end); + if (!(bottom <= range_from && range_to <= top)) { + ranges->Add(CharacterRange::Range(range_from, range_to), zone); + } + } + pos = end + 1; + } + } + } +#endif // V8_INTL_SUPPORT +} + +bool CharacterRange::IsCanonical(const ZoneList<CharacterRange>* ranges) { + DCHECK_NOT_NULL(ranges); + int n = ranges->length(); + if (n <= 1) return true; + base::uc32 max = ranges->at(0).to(); + for (int i = 1; i < n; i++) { + CharacterRange next_range = ranges->at(i); + if (next_range.from() <= max + 1) return false; + max = next_range.to(); + } + return true; +} + +ZoneList<CharacterRange>* CharacterSet::ranges(Zone* zone) { + if (ranges_ == nullptr) { + ranges_ = zone->New<ZoneList<CharacterRange>>(2, zone); + CharacterRange::AddClassEscape(standard_set_type_.value(), ranges_, false, + zone); + } + return ranges_; +} + +namespace { + +// Move a number of elements in a zonelist to another position +// in the same list. Handles overlapping source and target areas. +void MoveRanges(ZoneList<CharacterRange>* list, int from, int to, int count) { + // Ranges are potentially overlapping. + if (from < to) { + for (int i = count - 1; i >= 0; i--) { + list->at(to + i) = list->at(from + i); + } + } else { + for (int i = 0; i < count; i++) { + list->at(to + i) = list->at(from + i); + } + } +} + +int InsertRangeInCanonicalList(ZoneList<CharacterRange>* list, int count, + CharacterRange insert) { + // Inserts a range into list[0..count[, which must be sorted + // by from value and non-overlapping and non-adjacent, using at most + // list[0..count] for the result. Returns the number of resulting + // canonicalized ranges. Inserting a range may collapse existing ranges into + // fewer ranges, so the return value can be anything in the range 1..count+1. + base::uc32 from = insert.from(); + base::uc32 to = insert.to(); + int start_pos = 0; + int end_pos = count; + for (int i = count - 1; i >= 0; i--) { + CharacterRange current = list->at(i); + if (current.from() > to + 1) { + end_pos = i; + } else if (current.to() + 1 < from) { + start_pos = i + 1; + break; + } + } + + // Inserted range overlaps, or is adjacent to, ranges at positions + // [start_pos..end_pos[. Ranges before start_pos or at or after end_pos are + // not affected by the insertion. + // If start_pos == end_pos, the range must be inserted before start_pos. + // if start_pos < end_pos, the entire range from start_pos to end_pos + // must be merged with the insert range. + + if (start_pos == end_pos) { + // Insert between existing ranges at position start_pos. + if (start_pos < count) { + MoveRanges(list, start_pos, start_pos + 1, count - start_pos); + } + list->at(start_pos) = insert; + return count + 1; + } + if (start_pos + 1 == end_pos) { + // Replace single existing range at position start_pos. + CharacterRange to_replace = list->at(start_pos); + int new_from = std::min(to_replace.from(), from); + int new_to = std::max(to_replace.to(), to); + list->at(start_pos) = CharacterRange::Range(new_from, new_to); + return count; + } + // Replace a number of existing ranges from start_pos to end_pos - 1. + // Move the remaining ranges down. + + int new_from = std::min(list->at(start_pos).from(), from); + int new_to = std::max(list->at(end_pos - 1).to(), to); + if (end_pos < count) { + MoveRanges(list, end_pos, start_pos + 1, count - end_pos); + } + list->at(start_pos) = CharacterRange::Range(new_from, new_to); + return count - (end_pos - start_pos) + 1; +} + +} // namespace + +void CharacterSet::Canonicalize() { + // Special/default classes are always considered canonical. The result + // of calling ranges() will be sorted. + if (ranges_ == nullptr) return; + CharacterRange::Canonicalize(ranges_); +} + +// static +void CharacterRange::Canonicalize(ZoneList<CharacterRange>* character_ranges) { + if (character_ranges->length() <= 1) return; + // Check whether ranges are already canonical (increasing, non-overlapping, + // non-adjacent). + int n = character_ranges->length(); + base::uc32 max = character_ranges->at(0).to(); + int i = 1; + while (i < n) { + CharacterRange current = character_ranges->at(i); + if (current.from() <= max + 1) { + break; + } + max = current.to(); + i++; + } + // Canonical until the i'th range. If that's all of them, we are done. + if (i == n) return; + + // The ranges at index i and forward are not canonicalized. Make them so by + // doing the equivalent of insertion sort (inserting each into the previous + // list, in order). + // Notice that inserting a range can reduce the number of ranges in the + // result due to combining of adjacent and overlapping ranges. + int read = i; // Range to insert. + int num_canonical = i; // Length of canonicalized part of list. + do { + num_canonical = InsertRangeInCanonicalList(character_ranges, num_canonical, + character_ranges->at(read)); + read++; + } while (read < n); + character_ranges->Rewind(num_canonical); + + DCHECK(CharacterRange::IsCanonical(character_ranges)); +} + +// static +void CharacterRange::Negate(const ZoneList<CharacterRange>* ranges, + ZoneList<CharacterRange>* negated_ranges, + Zone* zone) { + DCHECK(CharacterRange::IsCanonical(ranges)); + DCHECK_EQ(0, negated_ranges->length()); + int range_count = ranges->length(); + base::uc32 from = 0; + int i = 0; + if (range_count > 0 && ranges->at(0).from() == 0) { + from = ranges->at(0).to() + 1; + i = 1; + } + while (i < range_count) { + CharacterRange range = ranges->at(i); + negated_ranges->Add(CharacterRange::Range(from, range.from() - 1), zone); + from = range.to() + 1; + i++; + } + if (from < kMaxCodePoint) { + negated_ranges->Add(CharacterRange::Range(from, kMaxCodePoint), zone); + } +} + +// static +void CharacterRange::Intersect(const ZoneList<CharacterRange>* lhs, + const ZoneList<CharacterRange>* rhs, + ZoneList<CharacterRange>* intersection, + Zone* zone) { + DCHECK(CharacterRange::IsCanonical(lhs)); + DCHECK(CharacterRange::IsCanonical(rhs)); + DCHECK_EQ(0, intersection->length()); + int lhs_index = 0; + int rhs_index = 0; + while (lhs_index < lhs->length() && rhs_index < rhs->length()) { + // Skip non-overlapping ranges. + if (lhs->at(lhs_index).to() < rhs->at(rhs_index).from()) { + lhs_index++; + continue; + } + if (rhs->at(rhs_index).to() < lhs->at(lhs_index).from()) { + rhs_index++; + continue; + } + + base::uc32 from = + std::max(lhs->at(lhs_index).from(), rhs->at(rhs_index).from()); + base::uc32 to = std::min(lhs->at(lhs_index).to(), rhs->at(rhs_index).to()); + intersection->Add(CharacterRange::Range(from, to), zone); + if (to == lhs->at(lhs_index).to()) { + lhs_index++; + } else { + rhs_index++; + } + } + + DCHECK(IsCanonical(intersection)); +} + +namespace { + +// Advance |index| and set |from| and |to| to the new range, if not out of +// bounds of |range|, otherwise |from| is set to a code point beyond the legal +// unicode character range. +void SafeAdvanceRange(const ZoneList<CharacterRange>* range, int* index, + base::uc32* from, base::uc32* to) { + ++(*index); + if (*index < range->length()) { + *from = range->at(*index).from(); + *to = range->at(*index).to(); + } else { + *from = kMaxCodePoint + 1; + } +} + +} // namespace + +// static +void CharacterRange::Subtract(const ZoneList<CharacterRange>* src, + const ZoneList<CharacterRange>* to_remove, + ZoneList<CharacterRange>* result, Zone* zone) { + DCHECK(CharacterRange::IsCanonical(src)); + DCHECK(CharacterRange::IsCanonical(to_remove)); + DCHECK_EQ(0, result->length()); + + if (src->is_empty()) return; + + int src_index = 0; + int to_remove_index = 0; + base::uc32 from = src->at(src_index).from(); + base::uc32 to = src->at(src_index).to(); + while (src_index < src->length() && to_remove_index < to_remove->length()) { + CharacterRange remove_range = to_remove->at(to_remove_index); + if (remove_range.to() < from) { + // (a) Non-overlapping case, ignore current to_remove range. + // |-------| + // |-------| + to_remove_index++; + } else if (to < remove_range.from()) { + // (b) Non-overlapping case, add full current range to result. + // |-------| + // |-------| + result->Add(CharacterRange::Range(from, to), zone); + SafeAdvanceRange(src, &src_index, &from, &to); + } else if (from >= remove_range.from() && to <= remove_range.to()) { + // (c) Current to_remove range fully covers current range. + // |---| + // |-------| + SafeAdvanceRange(src, &src_index, &from, &to); + } else if (from < remove_range.from() && to > remove_range.to()) { + // (d) Split current range. + // |-------| + // |---| + result->Add(CharacterRange::Range(from, remove_range.from() - 1), zone); + from = remove_range.to() + 1; + to_remove_index++; + } else if (from < remove_range.from()) { + // (e) End current range. + // |-------| + // |-------| + to = remove_range.from() - 1; + result->Add(CharacterRange::Range(from, to), zone); + SafeAdvanceRange(src, &src_index, &from, &to); + } else if (to > remove_range.to()) { + // (f) Modify start of current range. + // |-------| + // |-------| + from = remove_range.to() + 1; + to_remove_index++; + } else { + UNREACHABLE(); + } + } + // The last range needs special treatment after |to_remove| is exhausted, as + // |from| might have been modified by the last |to_remove| range and |to| was + // not yet known (i.e. cases d and f). + if (from <= to) { + result->Add(CharacterRange::Range(from, to), zone); + } + src_index++; + + // Add remaining ranges after |to_remove| is exhausted. + for (; src_index < src->length(); src_index++) { + result->Add(src->at(src_index), zone); + } + + DCHECK(IsCanonical(result)); +} + +// static +void CharacterRange::ClampToOneByte(ZoneList<CharacterRange>* ranges) { + DCHECK(IsCanonical(ranges)); + + // Drop all ranges that don't contain one-byte code units, and clamp the last + // range s.t. it likewise only contains one-byte code units. Note this relies + // on `ranges` being canonicalized, i.e. sorted and non-overlapping. + + static constexpr base::uc32 max_char = String::kMaxOneByteCharCodeU; + int n = ranges->length(); + for (; n > 0; n--) { + CharacterRange& r = ranges->at(n - 1); + if (r.from() <= max_char) { + r.to_ = std::min(r.to_, max_char); + break; + } + } + + ranges->Rewind(n); +} + +// static +bool CharacterRange::Equals(const ZoneList<CharacterRange>* lhs, + const ZoneList<CharacterRange>* rhs) { + DCHECK(IsCanonical(lhs)); + DCHECK(IsCanonical(rhs)); + if (lhs->length() != rhs->length()) return false; + + for (int i = 0; i < lhs->length(); i++) { + if (lhs->at(i) != rhs->at(i)) return false; + } + + return true; +} + +namespace { + +// Scoped object to keep track of how much we unroll quantifier loops in the +// regexp graph generator. +class RegExpExpansionLimiter { + public: + static const int kMaxExpansionFactor = 6; + RegExpExpansionLimiter(RegExpCompiler* compiler, int factor) + : compiler_(compiler), + saved_expansion_factor_(compiler->current_expansion_factor()), + ok_to_expand_(saved_expansion_factor_ <= kMaxExpansionFactor) { + DCHECK_LT(0, factor); + if (ok_to_expand_) { + if (factor > kMaxExpansionFactor) { + // Avoid integer overflow of the current expansion factor. + ok_to_expand_ = false; + compiler->set_current_expansion_factor(kMaxExpansionFactor + 1); + } else { + int new_factor = saved_expansion_factor_ * factor; + ok_to_expand_ = (new_factor <= kMaxExpansionFactor); + compiler->set_current_expansion_factor(new_factor); + } + } + } + + ~RegExpExpansionLimiter() { + compiler_->set_current_expansion_factor(saved_expansion_factor_); + } + + bool ok_to_expand() { return ok_to_expand_; } + + private: + RegExpCompiler* compiler_; + int saved_expansion_factor_; + bool ok_to_expand_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(RegExpExpansionLimiter); +}; + +} // namespace + +RegExpNode* RegExpQuantifier::ToNode(int min, int max, bool is_greedy, + RegExpTree* body, RegExpCompiler* compiler, + RegExpNode* on_success, + bool not_at_start) { + // x{f, t} becomes this: + // + // (r++)<-. + // | ` + // | (x) + // v ^ + // (r=0)-->(?)---/ [if r < t] + // | + // [if r >= f] \----> ... + // + + // 15.10.2.5 RepeatMatcher algorithm. + // The parser has already eliminated the case where max is 0. In the case + // where max_match is zero the parser has removed the quantifier if min was + // > 0 and removed the atom if min was 0. See AddQuantifierToAtom. + + // If we know that we cannot match zero length then things are a little + // simpler since we don't need to make the special zero length match check + // from step 2.1. If the min and max are small we can unroll a little in + // this case. + static const int kMaxUnrolledMinMatches = 3; // Unroll (foo)+ and (foo){3,} + static const int kMaxUnrolledMaxMatches = 3; // Unroll (foo)? and (foo){x,3} + if (max == 0) return on_success; // This can happen due to recursion. + bool body_can_be_empty = (body->min_match() == 0); + int body_start_reg = RegExpCompiler::kNoRegister; + Interval capture_registers = body->CaptureRegisters(); + bool needs_capture_clearing = !capture_registers.is_empty(); + Zone* zone = compiler->zone(); + + if (body_can_be_empty) { + body_start_reg = compiler->AllocateRegister(); + } else if (compiler->optimize() && !needs_capture_clearing) { + // Only unroll if there are no captures and the body can't be + // empty. + { + RegExpExpansionLimiter limiter(compiler, min + ((max != min) ? 1 : 0)); + if (min > 0 && min <= kMaxUnrolledMinMatches && limiter.ok_to_expand()) { + int new_max = (max == kInfinity) ? max : max - min; + // Recurse once to get the loop or optional matches after the fixed + // ones. + RegExpNode* answer = + ToNode(0, new_max, is_greedy, body, compiler, on_success, true); + // Unroll the forced matches from 0 to min. This can cause chains of + // TextNodes (which the parser does not generate). These should be + // combined if it turns out they hinder good code generation. + for (int i = 0; i < min; i++) { + answer = body->ToNode(compiler, answer); + } + return answer; + } + } + if (max <= kMaxUnrolledMaxMatches && min == 0) { + DCHECK_LT(0, max); // Due to the 'if' above. + RegExpExpansionLimiter limiter(compiler, max); + if (limiter.ok_to_expand()) { + // Unroll the optional matches up to max. + RegExpNode* answer = on_success; + for (int i = 0; i < max; i++) { + ChoiceNode* alternation = zone->New<ChoiceNode>(2, zone); + if (is_greedy) { + alternation->AddAlternative( + GuardedAlternative(body->ToNode(compiler, answer))); + alternation->AddAlternative(GuardedAlternative(on_success)); + } else { + alternation->AddAlternative(GuardedAlternative(on_success)); + alternation->AddAlternative( + GuardedAlternative(body->ToNode(compiler, answer))); + } + answer = alternation; + if (not_at_start && !compiler->read_backward()) { + alternation->set_not_at_start(); + } + } + return answer; + } + } + } + bool has_min = min > 0; + bool has_max = max < RegExpTree::kInfinity; + bool needs_counter = has_min || has_max; + int reg_ctr = needs_counter ? compiler->AllocateRegister() + : RegExpCompiler::kNoRegister; + LoopChoiceNode* center = zone->New<LoopChoiceNode>( + body->min_match() == 0, compiler->read_backward(), min, zone); + if (not_at_start && !compiler->read_backward()) center->set_not_at_start(); + RegExpNode* loop_return = + needs_counter ? static_cast<RegExpNode*>( + ActionNode::IncrementRegister(reg_ctr, center)) + : static_cast<RegExpNode*>(center); + if (body_can_be_empty) { + // If the body can be empty we need to check if it was and then + // backtrack. + loop_return = + ActionNode::EmptyMatchCheck(body_start_reg, reg_ctr, min, loop_return); + } + RegExpNode* body_node = body->ToNode(compiler, loop_return); + if (body_can_be_empty) { + // If the body can be empty we need to store the start position + // so we can bail out if it was empty. + body_node = ActionNode::StorePosition(body_start_reg, false, body_node); + } + if (needs_capture_clearing) { + // Before entering the body of this loop we need to clear captures. + body_node = ActionNode::ClearCaptures(capture_registers, body_node); + } + GuardedAlternative body_alt(body_node); + if (has_max) { + Guard* body_guard = zone->New<Guard>(reg_ctr, Guard::LT, max); + body_alt.AddGuard(body_guard, zone); + } + GuardedAlternative rest_alt(on_success); + if (has_min) { + Guard* rest_guard = compiler->zone()->New<Guard>(reg_ctr, Guard::GEQ, min); + rest_alt.AddGuard(rest_guard, zone); + } + if (is_greedy) { + center->AddLoopAlternative(body_alt); + center->AddContinueAlternative(rest_alt); + } else { + center->AddContinueAlternative(rest_alt); + center->AddLoopAlternative(body_alt); + } + if (needs_counter) { + return ActionNode::SetRegisterForLoop(reg_ctr, 0, center); + } else { + return center; + } +} + +} // namespace internal +} // namespace v8 diff --git a/js/src/irregexp/imported/regexp-compiler.cc b/js/src/irregexp/imported/regexp-compiler.cc new file mode 100644 index 0000000000..514975d8ed --- /dev/null +++ b/js/src/irregexp/imported/regexp-compiler.cc @@ -0,0 +1,3955 @@ +// 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-macro-assembler-arch.h" + +#ifdef V8_INTL_SUPPORT +#include "irregexp/imported/special-case.h" +#include "unicode/locid.h" +#include "unicode/uniset.h" +#include "unicode/utypes.h" +#endif // V8_INTL_SUPPORT + +namespace v8 { +namespace internal { + +using namespace regexp_compiler_constants; // NOLINT(build/namespaces) + +// ------------------------------------------------------------------- +// Implementation of the Irregexp regular expression engine. +// +// The Irregexp regular expression engine is intended to be a complete +// implementation of ECMAScript regular expressions. It generates either +// bytecodes or native code. + +// The Irregexp regexp engine is structured in three steps. +// 1) The parser generates an abstract syntax tree. See ast.cc. +// 2) From the AST a node network is created. The nodes are all +// subclasses of RegExpNode. The nodes represent states when +// executing a regular expression. Several optimizations are +// performed on the node network. +// 3) From the nodes we generate either byte codes or native code +// that can actually execute the regular expression (perform +// the search). The code generation step is described in more +// detail below. + +// Code generation. +// +// The nodes are divided into four main categories. +// * Choice nodes +// These represent places where the regular expression can +// match in more than one way. For example on entry to an +// alternation (foo|bar) or a repetition (*, +, ? or {}). +// * Action nodes +// These represent places where some action should be +// performed. Examples include recording the current position +// in the input string to a register (in order to implement +// captures) or other actions on register for example in order +// to implement the counters needed for {} repetitions. +// * Matching nodes +// These attempt to match some element part of the input string. +// Examples of elements include character classes, plain strings +// or back references. +// * End nodes +// These are used to implement the actions required on finding +// a successful match or failing to find a match. +// +// The code generated (whether as byte codes or native code) maintains +// some state as it runs. This consists of the following elements: +// +// * The capture registers. Used for string captures. +// * Other registers. Used for counters etc. +// * The current position. +// * The stack of backtracking information. Used when a matching node +// fails to find a match and needs to try an alternative. +// +// Conceptual regular expression execution model: +// +// There is a simple conceptual model of regular expression execution +// which will be presented first. The actual code generated is a more +// efficient simulation of the simple conceptual model: +// +// * Choice nodes are implemented as follows: +// For each choice except the last { +// push current position +// push backtrack code location +// <generate code to test for choice> +// backtrack code location: +// pop current position +// } +// <generate code to test for last choice> +// +// * Actions nodes are generated as follows +// <push affected registers on backtrack stack> +// <generate code to perform action> +// push backtrack code location +// <generate code to test for following nodes> +// backtrack code location: +// <pop affected registers to restore their state> +// <pop backtrack location from stack and go to it> +// +// * Matching nodes are generated as follows: +// if input string matches at current position +// update current position +// <generate code to test for following nodes> +// else +// <pop backtrack location from stack and go to it> +// +// Thus it can be seen that the current position is saved and restored +// by the choice nodes, whereas the registers are saved and restored by +// by the action nodes that manipulate them. +// +// The other interesting aspect of this model is that nodes are generated +// at the point where they are needed by a recursive call to Emit(). If +// the node has already been code generated then the Emit() call will +// generate a jump to the previously generated code instead. In order to +// limit recursion it is possible for the Emit() function to put the node +// on a work list for later generation and instead generate a jump. The +// destination of the jump is resolved later when the code is generated. +// +// Actual regular expression code generation. +// +// Code generation is actually more complicated than the above. In order to +// improve the efficiency of the generated code some optimizations are +// performed +// +// * Choice nodes have 1-character lookahead. +// A choice node looks at the following character and eliminates some of +// the choices immediately based on that character. This is not yet +// implemented. +// * Simple greedy loops store reduced backtracking information. +// A quantifier like /.*foo/m will greedily match the whole input. It will +// then need to backtrack to a point where it can match "foo". The naive +// implementation of this would push each character position onto the +// backtracking stack, then pop them off one by one. This would use space +// proportional to the length of the input string. However since the "." +// can only match in one way and always has a constant length (in this case +// of 1) it suffices to store the current position on the top of the stack +// once. Matching now becomes merely incrementing the current position and +// backtracking becomes decrementing the current position and checking the +// result against the stored current position. This is faster and saves +// space. +// * The current state is virtualized. +// This is used to defer expensive operations until it is clear that they +// are needed and to generate code for a node more than once, allowing +// specialized an efficient versions of the code to be created. This is +// explained in the section below. +// +// Execution state virtualization. +// +// Instead of emitting code, nodes that manipulate the state can record their +// manipulation in an object called the Trace. The Trace object can record a +// current position offset, an optional backtrack code location on the top of +// the virtualized backtrack stack and some register changes. When a node is +// to be emitted it can flush the Trace or update it. Flushing the Trace +// will emit code to bring the actual state into line with the virtual state. +// Avoiding flushing the state can postpone some work (e.g. updates of capture +// registers). Postponing work can save time when executing the regular +// expression since it may be found that the work never has to be done as a +// failure to match can occur. In addition it is much faster to jump to a +// known backtrack code location than it is to pop an unknown backtrack +// location from the stack and jump there. +// +// The virtual state found in the Trace affects code generation. For example +// the virtual state contains the difference between the actual current +// position and the virtual current position, and matching code needs to use +// this offset to attempt a match in the correct location of the input +// string. Therefore code generated for a non-trivial trace is specialized +// to that trace. The code generator therefore has the ability to generate +// code for each node several times. In order to limit the size of the +// generated code there is an arbitrary limit on how many specialized sets of +// code may be generated for a given node. If the limit is reached, the +// trace is flushed and a generic version of the code for a node is emitted. +// This is subsequently used for that node. The code emitted for non-generic +// trace is not recorded in the node and so it cannot currently be reused in +// the event that code generation is requested for an identical trace. + +namespace { + +constexpr base::uc32 MaxCodeUnit(const bool one_byte) { + static_assert(String::kMaxOneByteCharCodeU <= + std::numeric_limits<uint16_t>::max()); + static_assert(String::kMaxUtf16CodeUnitU <= + std::numeric_limits<uint16_t>::max()); + return one_byte ? String::kMaxOneByteCharCodeU : String::kMaxUtf16CodeUnitU; +} + +constexpr uint32_t CharMask(const bool one_byte) { + static_assert(base::bits::IsPowerOfTwo(String::kMaxOneByteCharCodeU + 1)); + static_assert(base::bits::IsPowerOfTwo(String::kMaxUtf16CodeUnitU + 1)); + return MaxCodeUnit(one_byte); +} + +} // namespace + +void RegExpTree::AppendToText(RegExpText* text, Zone* zone) { UNREACHABLE(); } + +void RegExpAtom::AppendToText(RegExpText* text, Zone* zone) { + text->AddElement(TextElement::Atom(this), zone); +} + +void RegExpClassRanges::AppendToText(RegExpText* text, Zone* zone) { + text->AddElement(TextElement::ClassRanges(this), zone); +} + +void RegExpText::AppendToText(RegExpText* text, Zone* zone) { + for (int i = 0; i < elements()->length(); i++) + text->AddElement(elements()->at(i), zone); +} + +TextElement TextElement::Atom(RegExpAtom* atom) { + return TextElement(ATOM, atom); +} + +TextElement TextElement::ClassRanges(RegExpClassRanges* class_ranges) { + return TextElement(CLASS_RANGES, class_ranges); +} + +int TextElement::length() const { + switch (text_type()) { + case ATOM: + return atom()->length(); + + case CLASS_RANGES: + return 1; + } + UNREACHABLE(); +} + +class RecursionCheck { + public: + explicit RecursionCheck(RegExpCompiler* compiler) : compiler_(compiler) { + compiler->IncrementRecursionDepth(); + } + ~RecursionCheck() { compiler_->DecrementRecursionDepth(); } + + private: + RegExpCompiler* compiler_; +}; + +// Attempts to compile the regexp using an Irregexp code generator. Returns +// a fixed array or a null handle depending on whether it succeeded. +RegExpCompiler::RegExpCompiler(Isolate* isolate, Zone* zone, int capture_count, + RegExpFlags flags, bool one_byte) + : next_register_(JSRegExp::RegistersForCaptureCount(capture_count)), + unicode_lookaround_stack_register_(kNoRegister), + unicode_lookaround_position_register_(kNoRegister), + work_list_(nullptr), + recursion_depth_(0), + flags_(flags), + one_byte_(one_byte), + reg_exp_too_big_(false), + limiting_recursion_(false), + optimize_(v8_flags.regexp_optimization), + read_backward_(false), + current_expansion_factor_(1), + frequency_collator_(), + isolate_(isolate), + zone_(zone) { + accept_ = zone->New<EndNode>(EndNode::ACCEPT, zone); + DCHECK_GE(RegExpMacroAssembler::kMaxRegister, next_register_ - 1); +} + +RegExpCompiler::CompilationResult RegExpCompiler::Assemble( + Isolate* isolate, RegExpMacroAssembler* macro_assembler, RegExpNode* start, + int capture_count, Handle<String> pattern) { + macro_assembler_ = macro_assembler; + + ZoneVector<RegExpNode*> work_list(zone()); + work_list_ = &work_list; + Label fail; + macro_assembler_->PushBacktrack(&fail); + Trace new_trace; + start->Emit(this, &new_trace); + macro_assembler_->BindJumpTarget(&fail); + macro_assembler_->Fail(); + while (!work_list.empty()) { + RegExpNode* node = work_list.back(); + work_list.pop_back(); + node->set_on_work_list(false); + if (!node->label()->is_bound()) node->Emit(this, &new_trace); + } + if (reg_exp_too_big_) { + if (v8_flags.correctness_fuzzer_suppressions) { + FATAL("Aborting on excess zone allocation"); + } + macro_assembler_->AbortedCodeGeneration(); + return CompilationResult::RegExpTooBig(); + } + + Handle<HeapObject> code = macro_assembler_->GetCode(pattern); + isolate->IncreaseTotalRegexpCodeGenerated(code); + work_list_ = nullptr; + + return {code, next_register_}; +} + +bool Trace::DeferredAction::Mentions(int that) { + if (action_type() == ActionNode::CLEAR_CAPTURES) { + Interval range = static_cast<DeferredClearCaptures*>(this)->range(); + return range.Contains(that); + } else { + return reg() == that; + } +} + +bool Trace::mentions_reg(int reg) { + for (DeferredAction* action = actions_; action != nullptr; + action = action->next()) { + if (action->Mentions(reg)) return true; + } + return false; +} + +bool Trace::GetStoredPosition(int reg, int* cp_offset) { + DCHECK_EQ(0, *cp_offset); + for (DeferredAction* action = actions_; action != nullptr; + action = action->next()) { + if (action->Mentions(reg)) { + if (action->action_type() == ActionNode::STORE_POSITION) { + *cp_offset = static_cast<DeferredCapture*>(action)->cp_offset(); + return true; + } else { + return false; + } + } + } + return false; +} + +// A (dynamically-sized) set of unsigned integers that behaves especially well +// on small integers (< kFirstLimit). May do zone-allocation. +class DynamicBitSet : public ZoneObject { + public: + V8_EXPORT_PRIVATE bool Get(unsigned value) const { + if (value < kFirstLimit) { + return (first_ & (1 << value)) != 0; + } else if (remaining_ == nullptr) { + return false; + } else { + return remaining_->Contains(value); + } + } + + // Destructively set a value in this set. + void Set(unsigned value, Zone* zone) { + if (value < kFirstLimit) { + first_ |= (1 << value); + } else { + if (remaining_ == nullptr) + remaining_ = zone->New<ZoneList<unsigned>>(1, zone); + if (remaining_->is_empty() || !remaining_->Contains(value)) + remaining_->Add(value, zone); + } + } + + private: + static constexpr unsigned kFirstLimit = 32; + + uint32_t first_ = 0; + ZoneList<unsigned>* remaining_ = nullptr; +}; + +int Trace::FindAffectedRegisters(DynamicBitSet* affected_registers, + Zone* zone) { + int max_register = RegExpCompiler::kNoRegister; + for (DeferredAction* action = actions_; action != nullptr; + action = action->next()) { + if (action->action_type() == ActionNode::CLEAR_CAPTURES) { + Interval range = static_cast<DeferredClearCaptures*>(action)->range(); + for (int i = range.from(); i <= range.to(); i++) + affected_registers->Set(i, zone); + if (range.to() > max_register) max_register = range.to(); + } else { + affected_registers->Set(action->reg(), zone); + if (action->reg() > max_register) max_register = action->reg(); + } + } + return max_register; +} + +void Trace::RestoreAffectedRegisters(RegExpMacroAssembler* assembler, + int max_register, + const DynamicBitSet& registers_to_pop, + const DynamicBitSet& registers_to_clear) { + for (int reg = max_register; reg >= 0; reg--) { + if (registers_to_pop.Get(reg)) { + assembler->PopRegister(reg); + } else if (registers_to_clear.Get(reg)) { + int clear_to = reg; + while (reg > 0 && registers_to_clear.Get(reg - 1)) { + reg--; + } + assembler->ClearRegisters(reg, clear_to); + } + } +} + +void Trace::PerformDeferredActions(RegExpMacroAssembler* assembler, + int max_register, + const DynamicBitSet& affected_registers, + DynamicBitSet* registers_to_pop, + DynamicBitSet* registers_to_clear, + Zone* zone) { + // The "+1" is to avoid a push_limit of zero if stack_limit_slack() is 1. + const int push_limit = (assembler->stack_limit_slack() + 1) / 2; + + // Count pushes performed to force a stack limit check occasionally. + int pushes = 0; + + for (int reg = 0; reg <= max_register; reg++) { + if (!affected_registers.Get(reg)) continue; + + // The chronologically first deferred action in the trace + // is used to infer the action needed to restore a register + // to its previous state (or not, if it's safe to ignore it). + enum DeferredActionUndoType { IGNORE, RESTORE, CLEAR }; + DeferredActionUndoType undo_action = IGNORE; + + int value = 0; + bool absolute = false; + bool clear = false; + static const int kNoStore = kMinInt; + int store_position = kNoStore; + // This is a little tricky because we are scanning the actions in reverse + // historical order (newest first). + for (DeferredAction* action = actions_; action != nullptr; + action = action->next()) { + if (action->Mentions(reg)) { + switch (action->action_type()) { + case ActionNode::SET_REGISTER_FOR_LOOP: { + Trace::DeferredSetRegisterForLoop* psr = + static_cast<Trace::DeferredSetRegisterForLoop*>(action); + if (!absolute) { + value += psr->value(); + absolute = true; + } + // SET_REGISTER_FOR_LOOP is only used for newly introduced loop + // counters. They can have a significant previous value if they + // occur in a loop. TODO(lrn): Propagate this information, so + // we can set undo_action to IGNORE if we know there is no value to + // restore. + undo_action = RESTORE; + DCHECK_EQ(store_position, kNoStore); + DCHECK(!clear); + break; + } + case ActionNode::INCREMENT_REGISTER: + if (!absolute) { + value++; + } + DCHECK_EQ(store_position, kNoStore); + DCHECK(!clear); + undo_action = RESTORE; + break; + case ActionNode::STORE_POSITION: { + Trace::DeferredCapture* pc = + static_cast<Trace::DeferredCapture*>(action); + if (!clear && store_position == kNoStore) { + store_position = pc->cp_offset(); + } + + // For captures we know that stores and clears alternate. + // Other register, are never cleared, and if the occur + // inside a loop, they might be assigned more than once. + if (reg <= 1) { + // Registers zero and one, aka "capture zero", is + // always set correctly if we succeed. There is no + // need to undo a setting on backtrack, because we + // will set it again or fail. + undo_action = IGNORE; + } else { + undo_action = pc->is_capture() ? CLEAR : RESTORE; + } + DCHECK(!absolute); + DCHECK_EQ(value, 0); + break; + } + case ActionNode::CLEAR_CAPTURES: { + // Since we're scanning in reverse order, if we've already + // set the position we have to ignore historically earlier + // clearing operations. + if (store_position == kNoStore) { + clear = true; + } + undo_action = RESTORE; + DCHECK(!absolute); + DCHECK_EQ(value, 0); + break; + } + default: + UNREACHABLE(); + } + } + } + // Prepare for the undo-action (e.g., push if it's going to be popped). + if (undo_action == RESTORE) { + pushes++; + RegExpMacroAssembler::StackCheckFlag stack_check = + RegExpMacroAssembler::kNoStackLimitCheck; + if (pushes == push_limit) { + stack_check = RegExpMacroAssembler::kCheckStackLimit; + pushes = 0; + } + + assembler->PushRegister(reg, stack_check); + registers_to_pop->Set(reg, zone); + } else if (undo_action == CLEAR) { + registers_to_clear->Set(reg, zone); + } + // Perform the chronologically last action (or accumulated increment) + // for the register. + if (store_position != kNoStore) { + assembler->WriteCurrentPositionToRegister(reg, store_position); + } else if (clear) { + assembler->ClearRegisters(reg, reg); + } else if (absolute) { + assembler->SetRegister(reg, value); + } else if (value != 0) { + assembler->AdvanceRegister(reg, value); + } + } +} + +// This is called as we come into a loop choice node and some other tricky +// nodes. It normalizes the state of the code generator to ensure we can +// generate generic code. +void Trace::Flush(RegExpCompiler* compiler, RegExpNode* successor) { + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + + DCHECK(!is_trivial()); + + if (actions_ == nullptr && backtrack() == nullptr) { + // Here we just have some deferred cp advances to fix and we are back to + // a normal situation. We may also have to forget some information gained + // through a quick check that was already performed. + if (cp_offset_ != 0) assembler->AdvanceCurrentPosition(cp_offset_); + // Create a new trivial state and generate the node with that. + Trace new_state; + successor->Emit(compiler, &new_state); + return; + } + + // Generate deferred actions here along with code to undo them again. + DynamicBitSet affected_registers; + + if (backtrack() != nullptr) { + // Here we have a concrete backtrack location. These are set up by choice + // nodes and so they indicate that we have a deferred save of the current + // position which we may need to emit here. + assembler->PushCurrentPosition(); + } + + int max_register = + FindAffectedRegisters(&affected_registers, compiler->zone()); + DynamicBitSet registers_to_pop; + DynamicBitSet registers_to_clear; + PerformDeferredActions(assembler, max_register, affected_registers, + ®isters_to_pop, ®isters_to_clear, + compiler->zone()); + if (cp_offset_ != 0) { + assembler->AdvanceCurrentPosition(cp_offset_); + } + + // Create a new trivial state and generate the node with that. + Label undo; + assembler->PushBacktrack(&undo); + if (successor->KeepRecursing(compiler)) { + Trace new_state; + successor->Emit(compiler, &new_state); + } else { + compiler->AddWork(successor); + assembler->GoTo(successor->label()); + } + + // On backtrack we need to restore state. + assembler->BindJumpTarget(&undo); + RestoreAffectedRegisters(assembler, max_register, registers_to_pop, + registers_to_clear); + if (backtrack() == nullptr) { + assembler->Backtrack(); + } else { + assembler->PopCurrentPosition(); + assembler->GoTo(backtrack()); + } +} + +void NegativeSubmatchSuccess::Emit(RegExpCompiler* compiler, Trace* trace) { + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + + // Omit flushing the trace. We discard the entire stack frame anyway. + + if (!label()->is_bound()) { + // We are completely independent of the trace, since we ignore it, + // so this code can be used as the generic version. + assembler->Bind(label()); + } + + // Throw away everything on the backtrack stack since the start + // of the negative submatch and restore the character position. + assembler->ReadCurrentPositionFromRegister(current_position_register_); + assembler->ReadStackPointerFromRegister(stack_pointer_register_); + if (clear_capture_count_ > 0) { + // Clear any captures that might have been performed during the success + // of the body of the negative look-ahead. + int clear_capture_end = clear_capture_start_ + clear_capture_count_ - 1; + assembler->ClearRegisters(clear_capture_start_, clear_capture_end); + } + // Now that we have unwound the stack we find at the top of the stack the + // backtrack that the BeginNegativeSubmatch node got. + assembler->Backtrack(); +} + +void EndNode::Emit(RegExpCompiler* compiler, Trace* trace) { + if (!trace->is_trivial()) { + trace->Flush(compiler, this); + return; + } + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + if (!label()->is_bound()) { + assembler->Bind(label()); + } + switch (action_) { + case ACCEPT: + assembler->Succeed(); + return; + case BACKTRACK: + assembler->GoTo(trace->backtrack()); + return; + case NEGATIVE_SUBMATCH_SUCCESS: + // This case is handled in a different virtual method. + UNREACHABLE(); + } + UNIMPLEMENTED(); +} + +void GuardedAlternative::AddGuard(Guard* guard, Zone* zone) { + if (guards_ == nullptr) guards_ = zone->New<ZoneList<Guard*>>(1, zone); + guards_->Add(guard, zone); +} + +ActionNode* ActionNode::SetRegisterForLoop(int reg, int val, + RegExpNode* on_success) { + ActionNode* result = + on_success->zone()->New<ActionNode>(SET_REGISTER_FOR_LOOP, on_success); + result->data_.u_store_register.reg = reg; + result->data_.u_store_register.value = val; + return result; +} + +ActionNode* ActionNode::IncrementRegister(int reg, RegExpNode* on_success) { + ActionNode* result = + on_success->zone()->New<ActionNode>(INCREMENT_REGISTER, on_success); + result->data_.u_increment_register.reg = reg; + return result; +} + +ActionNode* ActionNode::StorePosition(int reg, bool is_capture, + RegExpNode* on_success) { + ActionNode* result = + on_success->zone()->New<ActionNode>(STORE_POSITION, on_success); + result->data_.u_position_register.reg = reg; + result->data_.u_position_register.is_capture = is_capture; + return result; +} + +ActionNode* ActionNode::ClearCaptures(Interval range, RegExpNode* on_success) { + ActionNode* result = + on_success->zone()->New<ActionNode>(CLEAR_CAPTURES, on_success); + result->data_.u_clear_captures.range_from = range.from(); + result->data_.u_clear_captures.range_to = range.to(); + return result; +} + +ActionNode* ActionNode::BeginPositiveSubmatch(int stack_reg, int position_reg, + RegExpNode* on_success) { + ActionNode* result = + on_success->zone()->New<ActionNode>(BEGIN_POSITIVE_SUBMATCH, on_success); + result->data_.u_submatch.stack_pointer_register = stack_reg; + result->data_.u_submatch.current_position_register = position_reg; + return result; +} + +ActionNode* ActionNode::BeginNegativeSubmatch(int stack_reg, int position_reg, + RegExpNode* on_success) { + ActionNode* result = + on_success->zone()->New<ActionNode>(BEGIN_NEGATIVE_SUBMATCH, on_success); + result->data_.u_submatch.stack_pointer_register = stack_reg; + result->data_.u_submatch.current_position_register = position_reg; + return result; +} + +ActionNode* ActionNode::PositiveSubmatchSuccess(int stack_reg, int position_reg, + int clear_register_count, + int clear_register_from, + RegExpNode* on_success) { + ActionNode* result = on_success->zone()->New<ActionNode>( + POSITIVE_SUBMATCH_SUCCESS, on_success); + result->data_.u_submatch.stack_pointer_register = stack_reg; + result->data_.u_submatch.current_position_register = position_reg; + result->data_.u_submatch.clear_register_count = clear_register_count; + result->data_.u_submatch.clear_register_from = clear_register_from; + return result; +} + +ActionNode* ActionNode::EmptyMatchCheck(int start_register, + int repetition_register, + int repetition_limit, + RegExpNode* on_success) { + ActionNode* result = + on_success->zone()->New<ActionNode>(EMPTY_MATCH_CHECK, on_success); + result->data_.u_empty_match_check.start_register = start_register; + result->data_.u_empty_match_check.repetition_register = repetition_register; + result->data_.u_empty_match_check.repetition_limit = repetition_limit; + return result; +} + +#define DEFINE_ACCEPT(Type) \ + void Type##Node::Accept(NodeVisitor* visitor) { visitor->Visit##Type(this); } +FOR_EACH_NODE_TYPE(DEFINE_ACCEPT) +#undef DEFINE_ACCEPT + +// ------------------------------------------------------------------- +// Emit code. + +void ChoiceNode::GenerateGuard(RegExpMacroAssembler* macro_assembler, + Guard* guard, Trace* trace) { + switch (guard->op()) { + case Guard::LT: + DCHECK(!trace->mentions_reg(guard->reg())); + macro_assembler->IfRegisterGE(guard->reg(), guard->value(), + trace->backtrack()); + break; + case Guard::GEQ: + DCHECK(!trace->mentions_reg(guard->reg())); + macro_assembler->IfRegisterLT(guard->reg(), guard->value(), + trace->backtrack()); + break; + } +} + +namespace { + +#ifdef DEBUG +bool ContainsOnlyUtf16CodeUnits(unibrow::uchar* chars, int length) { + static_assert(sizeof(unibrow::uchar) == 4); + for (int i = 0; i < length; i++) { + if (chars[i] > String::kMaxUtf16CodeUnit) return false; + } + return true; +} +#endif // DEBUG + +// Returns the number of characters in the equivalence class, omitting those +// that cannot occur in the source string because it is Latin1. +int GetCaseIndependentLetters(Isolate* isolate, base::uc16 character, + bool one_byte_subject, unibrow::uchar* letters, + int letter_length) { +#ifdef V8_INTL_SUPPORT + if (RegExpCaseFolding::IgnoreSet().contains(character)) { + letters[0] = character; + DCHECK(ContainsOnlyUtf16CodeUnits(letters, 1)); + return 1; + } + bool in_special_add_set = + RegExpCaseFolding::SpecialAddSet().contains(character); + + icu::UnicodeSet set; + set.add(character); + set = set.closeOver(USET_CASE_INSENSITIVE); + + UChar32 canon = 0; + if (in_special_add_set) { + canon = RegExpCaseFolding::Canonicalize(character); + } + + int32_t range_count = set.getRangeCount(); + int items = 0; + for (int32_t i = 0; i < range_count; i++) { + UChar32 start = set.getRangeStart(i); + UChar32 end = set.getRangeEnd(i); + CHECK(end - start + items <= letter_length); + for (UChar32 cu = start; cu <= end; cu++) { + if (one_byte_subject && cu > String::kMaxOneByteCharCode) break; + if (in_special_add_set && RegExpCaseFolding::Canonicalize(cu) != canon) { + continue; + } + letters[items++] = static_cast<unibrow::uchar>(cu); + } + } + DCHECK(ContainsOnlyUtf16CodeUnits(letters, items)); + return items; +#else + int length = + isolate->jsregexp_uncanonicalize()->get(character, '\0', letters); + // Unibrow returns 0 or 1 for characters where case independence is + // trivial. + if (length == 0) { + letters[0] = character; + length = 1; + } + + if (one_byte_subject) { + int new_length = 0; + for (int i = 0; i < length; i++) { + if (letters[i] <= String::kMaxOneByteCharCode) { + letters[new_length++] = letters[i]; + } + } + length = new_length; + } + + DCHECK(ContainsOnlyUtf16CodeUnits(letters, length)); + return length; +#endif // V8_INTL_SUPPORT +} + +inline bool EmitSimpleCharacter(Isolate* isolate, RegExpCompiler* compiler, + base::uc16 c, Label* on_failure, int cp_offset, + bool check, bool preloaded) { + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + bool bound_checked = false; + if (!preloaded) { + assembler->LoadCurrentCharacter(cp_offset, on_failure, check); + bound_checked = true; + } + assembler->CheckNotCharacter(c, on_failure); + return bound_checked; +} + +// Only emits non-letters (things that don't have case). Only used for case +// independent matches. +inline bool EmitAtomNonLetter(Isolate* isolate, RegExpCompiler* compiler, + base::uc16 c, Label* on_failure, int cp_offset, + bool check, bool preloaded) { + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + bool one_byte = compiler->one_byte(); + unibrow::uchar chars[4]; + int length = GetCaseIndependentLetters(isolate, c, one_byte, chars, 4); + if (length < 1) { + // This can't match. Must be an one-byte subject and a non-one-byte + // character. We do not need to do anything since the one-byte pass + // already handled this. + return false; // Bounds not checked. + } + bool checked = false; + // We handle the length > 1 case in a later pass. + if (length == 1) { + if (one_byte && c > String::kMaxOneByteCharCodeU) { + // Can't match - see above. + return false; // Bounds not checked. + } + if (!preloaded) { + macro_assembler->LoadCurrentCharacter(cp_offset, on_failure, check); + checked = check; + } + macro_assembler->CheckNotCharacter(c, on_failure); + } + return checked; +} + +bool ShortCutEmitCharacterPair(RegExpMacroAssembler* macro_assembler, + bool one_byte, base::uc16 c1, base::uc16 c2, + Label* on_failure) { + const uint32_t char_mask = CharMask(one_byte); + base::uc16 exor = c1 ^ c2; + // Check whether exor has only one bit set. + if (((exor - 1) & exor) == 0) { + // If c1 and c2 differ only by one bit. + // Ecma262UnCanonicalize always gives the highest number last. + DCHECK(c2 > c1); + base::uc16 mask = char_mask ^ exor; + macro_assembler->CheckNotCharacterAfterAnd(c1, mask, on_failure); + return true; + } + DCHECK(c2 > c1); + base::uc16 diff = c2 - c1; + if (((diff - 1) & diff) == 0 && c1 >= diff) { + // If the characters differ by 2^n but don't differ by one bit then + // subtract the difference from the found character, then do the or + // trick. We avoid the theoretical case where negative numbers are + // involved in order to simplify code generation. + base::uc16 mask = char_mask ^ diff; + macro_assembler->CheckNotCharacterAfterMinusAnd(c1 - diff, diff, mask, + on_failure); + return true; + } + return false; +} + +// Only emits letters (things that have case). Only used for case independent +// matches. +inline bool EmitAtomLetter(Isolate* isolate, RegExpCompiler* compiler, + base::uc16 c, Label* on_failure, int cp_offset, + bool check, bool preloaded) { + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + bool one_byte = compiler->one_byte(); + unibrow::uchar chars[4]; + int length = GetCaseIndependentLetters(isolate, c, one_byte, chars, 4); + if (length <= 1) return false; + // We may not need to check against the end of the input string + // if this character lies before a character that matched. + if (!preloaded) { + macro_assembler->LoadCurrentCharacter(cp_offset, on_failure, check); + } + Label ok; + switch (length) { + case 2: { + if (ShortCutEmitCharacterPair(macro_assembler, one_byte, chars[0], + chars[1], on_failure)) { + } else { + macro_assembler->CheckCharacter(chars[0], &ok); + macro_assembler->CheckNotCharacter(chars[1], on_failure); + macro_assembler->Bind(&ok); + } + break; + } + case 4: + macro_assembler->CheckCharacter(chars[3], &ok); + V8_FALLTHROUGH; + case 3: + macro_assembler->CheckCharacter(chars[0], &ok); + macro_assembler->CheckCharacter(chars[1], &ok); + macro_assembler->CheckNotCharacter(chars[2], on_failure); + macro_assembler->Bind(&ok); + break; + default: + UNREACHABLE(); + } + return true; +} + +void EmitBoundaryTest(RegExpMacroAssembler* masm, int border, + Label* fall_through, Label* above_or_equal, + Label* below) { + if (below != fall_through) { + masm->CheckCharacterLT(border, below); + if (above_or_equal != fall_through) masm->GoTo(above_or_equal); + } else { + masm->CheckCharacterGT(border - 1, above_or_equal); + } +} + +void EmitDoubleBoundaryTest(RegExpMacroAssembler* masm, int first, int last, + Label* fall_through, Label* in_range, + Label* out_of_range) { + if (in_range == fall_through) { + if (first == last) { + masm->CheckNotCharacter(first, out_of_range); + } else { + masm->CheckCharacterNotInRange(first, last, out_of_range); + } + } else { + if (first == last) { + masm->CheckCharacter(first, in_range); + } else { + masm->CheckCharacterInRange(first, last, in_range); + } + if (out_of_range != fall_through) masm->GoTo(out_of_range); + } +} + +// even_label is for ranges[i] to ranges[i + 1] where i - start_index is even. +// odd_label is for ranges[i] to ranges[i + 1] where i - start_index is odd. +void EmitUseLookupTable(RegExpMacroAssembler* masm, + ZoneList<base::uc32>* ranges, uint32_t start_index, + uint32_t end_index, base::uc32 min_char, + Label* fall_through, Label* even_label, + Label* odd_label) { + static const uint32_t kSize = RegExpMacroAssembler::kTableSize; + static const uint32_t kMask = RegExpMacroAssembler::kTableMask; + + base::uc32 base = (min_char & ~kMask); + USE(base); + + // Assert that everything is on one kTableSize page. + for (uint32_t i = start_index; i <= end_index; i++) { + DCHECK_EQ(ranges->at(i) & ~kMask, base); + } + DCHECK(start_index == 0 || (ranges->at(start_index - 1) & ~kMask) <= base); + + char templ[kSize]; + Label* on_bit_set; + Label* on_bit_clear; + int bit; + if (even_label == fall_through) { + on_bit_set = odd_label; + on_bit_clear = even_label; + bit = 1; + } else { + on_bit_set = even_label; + on_bit_clear = odd_label; + bit = 0; + } + for (uint32_t i = 0; i < (ranges->at(start_index) & kMask) && i < kSize; + i++) { + templ[i] = bit; + } + uint32_t j = 0; + bit ^= 1; + for (uint32_t i = start_index; i < end_index; i++) { + for (j = (ranges->at(i) & kMask); j < (ranges->at(i + 1) & kMask); j++) { + templ[j] = bit; + } + bit ^= 1; + } + for (uint32_t i = j; i < kSize; i++) { + templ[i] = bit; + } + Factory* factory = masm->isolate()->factory(); + // TODO(erikcorry): Cache these. + Handle<ByteArray> ba = factory->NewByteArray(kSize, AllocationType::kOld); + for (uint32_t i = 0; i < kSize; i++) { + ba->set(i, templ[i]); + } + masm->CheckBitInTable(ba, on_bit_set); + if (on_bit_clear != fall_through) masm->GoTo(on_bit_clear); +} + +void CutOutRange(RegExpMacroAssembler* masm, ZoneList<base::uc32>* ranges, + uint32_t start_index, uint32_t end_index, uint32_t cut_index, + Label* even_label, Label* odd_label) { + bool odd = (((cut_index - start_index) & 1) == 1); + Label* in_range_label = odd ? odd_label : even_label; + Label dummy; + EmitDoubleBoundaryTest(masm, ranges->at(cut_index), + ranges->at(cut_index + 1) - 1, &dummy, in_range_label, + &dummy); + DCHECK(!dummy.is_linked()); + // Cut out the single range by rewriting the array. This creates a new + // range that is a merger of the two ranges on either side of the one we + // are cutting out. The oddity of the labels is preserved. + for (uint32_t j = cut_index; j > start_index; j--) { + ranges->at(j) = ranges->at(j - 1); + } + for (uint32_t j = cut_index + 1; j < end_index; j++) { + ranges->at(j) = ranges->at(j + 1); + } +} + +// Unicode case. Split the search space into kSize spaces that are handled +// with recursion. +void SplitSearchSpace(ZoneList<base::uc32>* ranges, uint32_t start_index, + uint32_t end_index, uint32_t* new_start_index, + uint32_t* new_end_index, base::uc32* border) { + static const uint32_t kSize = RegExpMacroAssembler::kTableSize; + static const uint32_t kMask = RegExpMacroAssembler::kTableMask; + + base::uc32 first = ranges->at(start_index); + base::uc32 last = ranges->at(end_index) - 1; + + *new_start_index = start_index; + *border = (ranges->at(start_index) & ~kMask) + kSize; + while (*new_start_index < end_index) { + if (ranges->at(*new_start_index) > *border) break; + (*new_start_index)++; + } + // new_start_index is the index of the first edge that is beyond the + // current kSize space. + + // For very large search spaces we do a binary chop search of the non-Latin1 + // space instead of just going to the end of the current kSize space. The + // heuristics are complicated a little by the fact that any 128-character + // encoding space can be quickly tested with a table lookup, so we don't + // wish to do binary chop search at a smaller granularity than that. A + // 128-character space can take up a lot of space in the ranges array if, + // for example, we only want to match every second character (eg. the lower + // case characters on some Unicode pages). + uint32_t binary_chop_index = (end_index + start_index) / 2; + // The first test ensures that we get to the code that handles the Latin1 + // range with a single not-taken branch, speeding up this important + // character range (even non-Latin1 charset-based text has spaces and + // punctuation). + if (*border - 1 > String::kMaxOneByteCharCode && // Latin1 case. + end_index - start_index > (*new_start_index - start_index) * 2 && + last - first > kSize * 2 && binary_chop_index > *new_start_index && + ranges->at(binary_chop_index) >= first + 2 * kSize) { + uint32_t scan_forward_for_section_border = binary_chop_index; + uint32_t new_border = (ranges->at(binary_chop_index) | kMask) + 1; + + while (scan_forward_for_section_border < end_index) { + if (ranges->at(scan_forward_for_section_border) > new_border) { + *new_start_index = scan_forward_for_section_border; + *border = new_border; + break; + } + scan_forward_for_section_border++; + } + } + + DCHECK(*new_start_index > start_index); + *new_end_index = *new_start_index - 1; + if (ranges->at(*new_end_index) == *border) { + (*new_end_index)--; + } + if (*border >= ranges->at(end_index)) { + *border = ranges->at(end_index); + *new_start_index = end_index; // Won't be used. + *new_end_index = end_index - 1; + } +} + +// Gets a series of segment boundaries representing a character class. If the +// character is in the range between an even and an odd boundary (counting from +// start_index) then go to even_label, otherwise go to odd_label. We already +// know that the character is in the range of min_char to max_char inclusive. +// Either label can be nullptr indicating backtracking. Either label can also +// be equal to the fall_through label. +void GenerateBranches(RegExpMacroAssembler* masm, ZoneList<base::uc32>* ranges, + uint32_t start_index, uint32_t end_index, + base::uc32 min_char, base::uc32 max_char, + Label* fall_through, Label* even_label, + Label* odd_label) { + DCHECK_LE(min_char, String::kMaxUtf16CodeUnit); + DCHECK_LE(max_char, String::kMaxUtf16CodeUnit); + + base::uc32 first = ranges->at(start_index); + base::uc32 last = ranges->at(end_index) - 1; + + DCHECK_LT(min_char, first); + + // Just need to test if the character is before or on-or-after + // a particular character. + if (start_index == end_index) { + EmitBoundaryTest(masm, first, fall_through, even_label, odd_label); + return; + } + + // Another almost trivial case: There is one interval in the middle that is + // different from the end intervals. + if (start_index + 1 == end_index) { + EmitDoubleBoundaryTest(masm, first, last, fall_through, even_label, + odd_label); + return; + } + + // It's not worth using table lookup if there are very few intervals in the + // character class. + if (end_index - start_index <= 6) { + // It is faster to test for individual characters, so we look for those + // first, then try arbitrary ranges in the second round. + static uint32_t kNoCutIndex = -1; + uint32_t cut = kNoCutIndex; + for (uint32_t i = start_index; i < end_index; i++) { + if (ranges->at(i) == ranges->at(i + 1) - 1) { + cut = i; + break; + } + } + if (cut == kNoCutIndex) cut = start_index; + CutOutRange(masm, ranges, start_index, end_index, cut, even_label, + odd_label); + DCHECK_GE(end_index - start_index, 2); + GenerateBranches(masm, ranges, start_index + 1, end_index - 1, min_char, + max_char, fall_through, even_label, odd_label); + return; + } + + // If there are a lot of intervals in the regexp, then we will use tables to + // determine whether the character is inside or outside the character class. + static const int kBits = RegExpMacroAssembler::kTableSizeBits; + + if ((max_char >> kBits) == (min_char >> kBits)) { + EmitUseLookupTable(masm, ranges, start_index, end_index, min_char, + fall_through, even_label, odd_label); + return; + } + + if ((min_char >> kBits) != first >> kBits) { + masm->CheckCharacterLT(first, odd_label); + GenerateBranches(masm, ranges, start_index + 1, end_index, first, max_char, + fall_through, odd_label, even_label); + return; + } + + uint32_t new_start_index = 0; + uint32_t new_end_index = 0; + base::uc32 border = 0; + + SplitSearchSpace(ranges, start_index, end_index, &new_start_index, + &new_end_index, &border); + + Label handle_rest; + Label* above = &handle_rest; + if (border == last + 1) { + // We didn't find any section that started after the limit, so everything + // above the border is one of the terminal labels. + above = (end_index & 1) != (start_index & 1) ? odd_label : even_label; + DCHECK(new_end_index == end_index - 1); + } + + DCHECK_LE(start_index, new_end_index); + DCHECK_LE(new_start_index, end_index); + DCHECK_LT(start_index, new_start_index); + DCHECK_LT(new_end_index, end_index); + DCHECK(new_end_index + 1 == new_start_index || + (new_end_index + 2 == new_start_index && + border == ranges->at(new_end_index + 1))); + DCHECK_LT(min_char, border - 1); + DCHECK_LT(border, max_char); + DCHECK_LT(ranges->at(new_end_index), border); + DCHECK(border < ranges->at(new_start_index) || + (border == ranges->at(new_start_index) && + new_start_index == end_index && new_end_index == end_index - 1 && + border == last + 1)); + DCHECK(new_start_index == 0 || border >= ranges->at(new_start_index - 1)); + + masm->CheckCharacterGT(border - 1, above); + Label dummy; + GenerateBranches(masm, ranges, start_index, new_end_index, min_char, + border - 1, &dummy, even_label, odd_label); + if (handle_rest.is_linked()) { + masm->Bind(&handle_rest); + bool flip = (new_start_index & 1) != (start_index & 1); + GenerateBranches(masm, ranges, new_start_index, end_index, border, max_char, + &dummy, flip ? odd_label : even_label, + flip ? even_label : odd_label); + } +} + +void EmitClassRanges(RegExpMacroAssembler* macro_assembler, + RegExpClassRanges* cr, bool one_byte, Label* on_failure, + int cp_offset, bool check_offset, bool preloaded, + Zone* zone) { + ZoneList<CharacterRange>* ranges = cr->ranges(zone); + CharacterRange::Canonicalize(ranges); + + // Now that all processing (like case-insensitivity) is done, clamp the + // ranges to the set of ranges that may actually occur in the subject string. + if (one_byte) CharacterRange::ClampToOneByte(ranges); + + const int ranges_length = ranges->length(); + if (ranges_length == 0) { + if (!cr->is_negated()) { + macro_assembler->GoTo(on_failure); + } + if (check_offset) { + macro_assembler->CheckPosition(cp_offset, on_failure); + } + return; + } + + const base::uc32 max_char = MaxCodeUnit(one_byte); + if (ranges_length == 1 && ranges->at(0).IsEverything(max_char)) { + if (cr->is_negated()) { + macro_assembler->GoTo(on_failure); + } else { + // This is a common case hit by non-anchored expressions. + if (check_offset) { + macro_assembler->CheckPosition(cp_offset, on_failure); + } + } + return; + } + + if (!preloaded) { + macro_assembler->LoadCurrentCharacter(cp_offset, on_failure, check_offset); + } + + if (cr->is_standard(zone) && macro_assembler->CheckSpecialClassRanges( + cr->standard_type(), on_failure)) { + return; + } + + static constexpr int kMaxRangesForInlineBranchGeneration = 16; + if (ranges_length > kMaxRangesForInlineBranchGeneration) { + // For large range sets, emit a more compact instruction sequence to avoid + // a potentially problematic increase in code size. + // Note the flipped logic below (we check InRange if negated, NotInRange if + // not negated); this is necessary since the method falls through on + // failure whereas we want to fall through on success. + if (cr->is_negated()) { + if (macro_assembler->CheckCharacterInRangeArray(ranges, on_failure)) { + return; + } + } else { + if (macro_assembler->CheckCharacterNotInRangeArray(ranges, on_failure)) { + return; + } + } + } + + // Generate a flat list of range boundaries for consumption by + // GenerateBranches. See the comment on that function for how the list should + // be structured + ZoneList<base::uc32>* range_boundaries = + zone->New<ZoneList<base::uc32>>(ranges_length * 2, zone); + + bool zeroth_entry_is_failure = !cr->is_negated(); + + for (int i = 0; i < ranges_length; i++) { + CharacterRange& range = ranges->at(i); + if (range.from() == 0) { + DCHECK_EQ(i, 0); + zeroth_entry_is_failure = !zeroth_entry_is_failure; + } else { + range_boundaries->Add(range.from(), zone); + } + // `+ 1` to convert from inclusive to exclusive `to`. + // [from, to] == [from, to+1[. + range_boundaries->Add(range.to() + 1, zone); + } + int end_index = range_boundaries->length() - 1; + if (range_boundaries->at(end_index) > max_char) { + end_index--; + } + + Label fall_through; + GenerateBranches(macro_assembler, range_boundaries, + 0, // start_index. + end_index, + 0, // min_char. + max_char, &fall_through, + zeroth_entry_is_failure ? &fall_through : on_failure, + zeroth_entry_is_failure ? on_failure : &fall_through); + macro_assembler->Bind(&fall_through); +} + +} // namespace + +RegExpNode::~RegExpNode() = default; + +RegExpNode::LimitResult RegExpNode::LimitVersions(RegExpCompiler* compiler, + Trace* trace) { + // If we are generating a greedy loop then don't stop and don't reuse code. + if (trace->stop_node() != nullptr) { + return CONTINUE; + } + + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + if (trace->is_trivial()) { + if (label_.is_bound() || on_work_list() || !KeepRecursing(compiler)) { + // If a generic version is already scheduled to be generated or we have + // recursed too deeply then just generate a jump to that code. + macro_assembler->GoTo(&label_); + // This will queue it up for generation of a generic version if it hasn't + // already been queued. + compiler->AddWork(this); + return DONE; + } + // Generate generic version of the node and bind the label for later use. + macro_assembler->Bind(&label_); + return CONTINUE; + } + + // We are being asked to make a non-generic version. Keep track of how many + // non-generic versions we generate so as not to overdo it. + trace_count_++; + if (KeepRecursing(compiler) && compiler->optimize() && + trace_count_ < kMaxCopiesCodeGenerated) { + return CONTINUE; + } + + // If we get here code has been generated for this node too many times or + // recursion is too deep. Time to switch to a generic version. The code for + // generic versions above can handle deep recursion properly. + bool was_limiting = compiler->limiting_recursion(); + compiler->set_limiting_recursion(true); + trace->Flush(compiler, this); + compiler->set_limiting_recursion(was_limiting); + return DONE; +} + +bool RegExpNode::KeepRecursing(RegExpCompiler* compiler) { + return !compiler->limiting_recursion() && + compiler->recursion_depth() <= RegExpCompiler::kMaxRecursion; +} + +void ActionNode::FillInBMInfo(Isolate* isolate, int offset, int budget, + BoyerMooreLookahead* bm, bool not_at_start) { + if (action_type_ == POSITIVE_SUBMATCH_SUCCESS) { + // Anything may follow a positive submatch success, thus we need to accept + // all characters from this position onwards. + bm->SetRest(offset); + } else { + on_success()->FillInBMInfo(isolate, offset, budget - 1, bm, not_at_start); + } + SaveBMInfo(bm, not_at_start, offset); +} + +void ActionNode::GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, int filled_in, + bool not_at_start) { + if (action_type_ == SET_REGISTER_FOR_LOOP) { + on_success()->GetQuickCheckDetailsFromLoopEntry(details, compiler, + filled_in, not_at_start); + } else { + on_success()->GetQuickCheckDetails(details, compiler, filled_in, + not_at_start); + } +} + +void AssertionNode::FillInBMInfo(Isolate* isolate, int offset, int budget, + BoyerMooreLookahead* bm, bool not_at_start) { + // Match the behaviour of EatsAtLeast on this node. + if (assertion_type() == AT_START && not_at_start) return; + on_success()->FillInBMInfo(isolate, offset, budget - 1, bm, not_at_start); + SaveBMInfo(bm, not_at_start, offset); +} + +void NegativeLookaroundChoiceNode::GetQuickCheckDetails( + QuickCheckDetails* details, RegExpCompiler* compiler, int filled_in, + bool not_at_start) { + RegExpNode* node = continue_node(); + return node->GetQuickCheckDetails(details, compiler, filled_in, not_at_start); +} + +namespace { + +// Takes the left-most 1-bit and smears it out, setting all bits to its right. +inline uint32_t SmearBitsRight(uint32_t v) { + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + return v; +} + +} // namespace + +bool QuickCheckDetails::Rationalize(bool asc) { + bool found_useful_op = false; + const uint32_t char_mask = CharMask(asc); + mask_ = 0; + value_ = 0; + int char_shift = 0; + for (int i = 0; i < characters_; i++) { + Position* pos = &positions_[i]; + if ((pos->mask & String::kMaxOneByteCharCode) != 0) { + found_useful_op = true; + } + mask_ |= (pos->mask & char_mask) << char_shift; + value_ |= (pos->value & char_mask) << char_shift; + char_shift += asc ? 8 : 16; + } + return found_useful_op; +} + +int RegExpNode::EatsAtLeast(bool not_at_start) { + return not_at_start ? eats_at_least_.eats_at_least_from_not_start + : eats_at_least_.eats_at_least_from_possibly_start; +} + +EatsAtLeastInfo RegExpNode::EatsAtLeastFromLoopEntry() { + // SET_REGISTER_FOR_LOOP is only used to initialize loop counters, and it + // implies that the following node must be a LoopChoiceNode. If we need to + // set registers to constant values for other reasons, we could introduce a + // new action type SET_REGISTER that doesn't imply anything about its + // successor. + UNREACHABLE(); +} + +void RegExpNode::GetQuickCheckDetailsFromLoopEntry(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start) { + // See comment in RegExpNode::EatsAtLeastFromLoopEntry. + UNREACHABLE(); +} + +EatsAtLeastInfo LoopChoiceNode::EatsAtLeastFromLoopEntry() { + DCHECK_EQ(alternatives_->length(), 2); // There's just loop and continue. + + if (read_backward()) { + // The eats_at_least value is not used if reading backward. The + // EatsAtLeastPropagator should've zeroed it as well. + DCHECK_EQ(eats_at_least_info()->eats_at_least_from_possibly_start, 0); + DCHECK_EQ(eats_at_least_info()->eats_at_least_from_not_start, 0); + return {}; + } + + // Figure out how much the loop body itself eats, not including anything in + // the continuation case. In general, the nodes in the loop body should report + // that they eat at least the number eaten by the continuation node, since any + // successful match in the loop body must also include the continuation node. + // However, in some cases involving positive lookaround, the loop body under- + // reports its appetite, so use saturated math here to avoid negative numbers. + uint8_t loop_body_from_not_start = base::saturated_cast<uint8_t>( + loop_node_->EatsAtLeast(true) - continue_node_->EatsAtLeast(true)); + uint8_t loop_body_from_possibly_start = base::saturated_cast<uint8_t>( + loop_node_->EatsAtLeast(false) - continue_node_->EatsAtLeast(true)); + + // Limit the number of loop iterations to avoid overflow in subsequent steps. + int loop_iterations = base::saturated_cast<uint8_t>(min_loop_iterations()); + + EatsAtLeastInfo result; + result.eats_at_least_from_not_start = + base::saturated_cast<uint8_t>(loop_iterations * loop_body_from_not_start + + continue_node_->EatsAtLeast(true)); + if (loop_iterations > 0 && loop_body_from_possibly_start > 0) { + // First loop iteration eats at least one, so all subsequent iterations + // and the after-loop chunk are guaranteed to not be at the start. + result.eats_at_least_from_possibly_start = base::saturated_cast<uint8_t>( + loop_body_from_possibly_start + + (loop_iterations - 1) * loop_body_from_not_start + + continue_node_->EatsAtLeast(true)); + } else { + // Loop body might eat nothing, so only continue node contributes. + result.eats_at_least_from_possibly_start = + continue_node_->EatsAtLeast(false); + } + return result; +} + +bool RegExpNode::EmitQuickCheck(RegExpCompiler* compiler, + Trace* bounds_check_trace, Trace* trace, + bool preload_has_checked_bounds, + Label* on_possible_success, + QuickCheckDetails* details, + bool fall_through_on_failure, + ChoiceNode* predecessor) { + DCHECK_NOT_NULL(predecessor); + if (details->characters() == 0) return false; + GetQuickCheckDetails(details, compiler, 0, + trace->at_start() == Trace::FALSE_VALUE); + if (details->cannot_match()) return false; + if (!details->Rationalize(compiler->one_byte())) return false; + DCHECK(details->characters() == 1 || + compiler->macro_assembler()->CanReadUnaligned()); + uint32_t mask = details->mask(); + uint32_t value = details->value(); + + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + + if (trace->characters_preloaded() != details->characters()) { + DCHECK(trace->cp_offset() == bounds_check_trace->cp_offset()); + // The bounds check is performed using the minimum number of characters + // any choice would eat, so if the bounds check fails, then none of the + // choices can succeed, so we can just immediately backtrack, rather + // than go to the next choice. The number of characters preloaded may be + // less than the number used for the bounds check. + int eats_at_least = predecessor->EatsAtLeast( + bounds_check_trace->at_start() == Trace::FALSE_VALUE); + DCHECK_GE(eats_at_least, details->characters()); + assembler->LoadCurrentCharacter( + trace->cp_offset(), bounds_check_trace->backtrack(), + !preload_has_checked_bounds, details->characters(), eats_at_least); + } + + bool need_mask = true; + + if (details->characters() == 1) { + // If number of characters preloaded is 1 then we used a byte or 16 bit + // load so the value is already masked down. + const uint32_t char_mask = CharMask(compiler->one_byte()); + if ((mask & char_mask) == char_mask) need_mask = false; + mask &= char_mask; + } else { + // For 2-character preloads in one-byte mode or 1-character preloads in + // two-byte mode we also use a 16 bit load with zero extend. + static const uint32_t kTwoByteMask = 0xFFFF; + static const uint32_t kFourByteMask = 0xFFFFFFFF; + if (details->characters() == 2 && compiler->one_byte()) { + if ((mask & kTwoByteMask) == kTwoByteMask) need_mask = false; + } else if (details->characters() == 1 && !compiler->one_byte()) { + if ((mask & kTwoByteMask) == kTwoByteMask) need_mask = false; + } else { + if (mask == kFourByteMask) need_mask = false; + } + } + + if (fall_through_on_failure) { + if (need_mask) { + assembler->CheckCharacterAfterAnd(value, mask, on_possible_success); + } else { + assembler->CheckCharacter(value, on_possible_success); + } + } else { + if (need_mask) { + assembler->CheckNotCharacterAfterAnd(value, mask, trace->backtrack()); + } else { + assembler->CheckNotCharacter(value, trace->backtrack()); + } + } + return true; +} + +// Here is the meat of GetQuickCheckDetails (see also the comment on the +// super-class in the .h file). +// +// We iterate along the text object, building up for each character a +// mask and value that can be used to test for a quick failure to match. +// The masks and values for the positions will be combined into a single +// machine word for the current character width in order to be used in +// generating a quick check. +void TextNode::GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start) { + // Do not collect any quick check details if the text node reads backward, + // since it reads in the opposite direction than we use for quick checks. + if (read_backward()) return; + Isolate* isolate = compiler->macro_assembler()->isolate(); + DCHECK(characters_filled_in < details->characters()); + int characters = details->characters(); + const uint32_t char_mask = CharMask(compiler->one_byte()); + for (int k = 0; k < elements()->length(); k++) { + TextElement elm = elements()->at(k); + if (elm.text_type() == TextElement::ATOM) { + base::Vector<const base::uc16> quarks = elm.atom()->data(); + for (int i = 0; i < characters && i < quarks.length(); i++) { + QuickCheckDetails::Position* pos = + details->positions(characters_filled_in); + base::uc16 c = quarks[i]; + if (IsIgnoreCase(compiler->flags())) { + unibrow::uchar chars[4]; + int length = GetCaseIndependentLetters( + isolate, c, compiler->one_byte(), chars, 4); + if (length == 0) { + // This can happen because all case variants are non-Latin1, but we + // know the input is Latin1. + details->set_cannot_match(); + pos->determines_perfectly = false; + return; + } + if (length == 1) { + // This letter has no case equivalents, so it's nice and simple + // and the mask-compare will determine definitely whether we have + // a match at this character position. + pos->mask = char_mask; + pos->value = chars[0]; + pos->determines_perfectly = true; + } else { + uint32_t common_bits = char_mask; + uint32_t bits = chars[0]; + for (int j = 1; j < length; j++) { + uint32_t differing_bits = ((chars[j] & common_bits) ^ bits); + common_bits ^= differing_bits; + bits &= common_bits; + } + // If length is 2 and common bits has only one zero in it then + // our mask and compare instruction will determine definitely + // whether we have a match at this character position. Otherwise + // it can only be an approximate check. + uint32_t one_zero = (common_bits | ~char_mask); + if (length == 2 && ((~one_zero) & ((~one_zero) - 1)) == 0) { + pos->determines_perfectly = true; + } + pos->mask = common_bits; + pos->value = bits; + } + } else { + // Don't ignore case. Nice simple case where the mask-compare will + // determine definitely whether we have a match at this character + // position. + if (c > char_mask) { + details->set_cannot_match(); + pos->determines_perfectly = false; + return; + } + pos->mask = char_mask; + pos->value = c; + pos->determines_perfectly = true; + } + characters_filled_in++; + DCHECK(characters_filled_in <= details->characters()); + if (characters_filled_in == details->characters()) { + return; + } + } + } else { + QuickCheckDetails::Position* pos = + details->positions(characters_filled_in); + RegExpClassRanges* tree = elm.class_ranges(); + ZoneList<CharacterRange>* ranges = tree->ranges(zone()); + if (tree->is_negated() || ranges->is_empty()) { + // A quick check uses multi-character mask and compare. There is no + // useful way to incorporate a negative char class into this scheme + // so we just conservatively create a mask and value that will always + // succeed. + // Likewise for empty ranges (empty ranges can occur e.g. when + // compiling for one-byte subjects and impossible (non-one-byte) ranges + // have been removed). + pos->mask = 0; + pos->value = 0; + } else { + int first_range = 0; + while (ranges->at(first_range).from() > char_mask) { + first_range++; + if (first_range == ranges->length()) { + details->set_cannot_match(); + pos->determines_perfectly = false; + return; + } + } + CharacterRange range = ranges->at(first_range); + const base::uc32 first_from = range.from(); + const base::uc32 first_to = + (range.to() > char_mask) ? char_mask : range.to(); + const uint32_t differing_bits = (first_from ^ first_to); + // A mask and compare is only perfect if the differing bits form a + // number like 00011111 with one single block of trailing 1s. + if ((differing_bits & (differing_bits + 1)) == 0 && + first_from + differing_bits == first_to) { + pos->determines_perfectly = true; + } + uint32_t common_bits = ~SmearBitsRight(differing_bits); + uint32_t bits = (first_from & common_bits); + for (int i = first_range + 1; i < ranges->length(); i++) { + range = ranges->at(i); + const base::uc32 from = range.from(); + if (from > char_mask) continue; + const base::uc32 to = + (range.to() > char_mask) ? char_mask : range.to(); + // Here we are combining more ranges into the mask and compare + // value. With each new range the mask becomes more sparse and + // so the chances of a false positive rise. A character class + // with multiple ranges is assumed never to be equivalent to a + // mask and compare operation. + pos->determines_perfectly = false; + uint32_t new_common_bits = (from ^ to); + new_common_bits = ~SmearBitsRight(new_common_bits); + common_bits &= new_common_bits; + bits &= new_common_bits; + uint32_t new_differing_bits = (from & common_bits) ^ bits; + common_bits ^= new_differing_bits; + bits &= common_bits; + } + pos->mask = common_bits; + pos->value = bits; + } + characters_filled_in++; + DCHECK(characters_filled_in <= details->characters()); + if (characters_filled_in == details->characters()) return; + } + } + DCHECK(characters_filled_in != details->characters()); + if (!details->cannot_match()) { + on_success()->GetQuickCheckDetails(details, compiler, characters_filled_in, + true); + } +} + +void QuickCheckDetails::Clear() { + for (int i = 0; i < characters_; i++) { + positions_[i].mask = 0; + positions_[i].value = 0; + positions_[i].determines_perfectly = false; + } + characters_ = 0; +} + +void QuickCheckDetails::Advance(int by, bool one_byte) { + if (by >= characters_ || by < 0) { + DCHECK_IMPLIES(by < 0, characters_ == 0); + Clear(); + return; + } + DCHECK_LE(characters_ - by, 4); + DCHECK_LE(characters_, 4); + for (int i = 0; i < characters_ - by; i++) { + positions_[i] = positions_[by + i]; + } + for (int i = characters_ - by; i < characters_; i++) { + positions_[i].mask = 0; + positions_[i].value = 0; + positions_[i].determines_perfectly = false; + } + characters_ -= by; + // We could change mask_ and value_ here but we would never advance unless + // they had already been used in a check and they won't be used again because + // it would gain us nothing. So there's no point. +} + +void QuickCheckDetails::Merge(QuickCheckDetails* other, int from_index) { + DCHECK(characters_ == other->characters_); + if (other->cannot_match_) { + return; + } + if (cannot_match_) { + *this = *other; + return; + } + for (int i = from_index; i < characters_; i++) { + QuickCheckDetails::Position* pos = positions(i); + QuickCheckDetails::Position* other_pos = other->positions(i); + if (pos->mask != other_pos->mask || pos->value != other_pos->value || + !other_pos->determines_perfectly) { + // Our mask-compare operation will be approximate unless we have the + // exact same operation on both sides of the alternation. + pos->determines_perfectly = false; + } + pos->mask &= other_pos->mask; + pos->value &= pos->mask; + other_pos->value &= pos->mask; + uint32_t differing_bits = (pos->value ^ other_pos->value); + pos->mask &= ~differing_bits; + pos->value &= pos->mask; + } +} + +class VisitMarker { + public: + explicit VisitMarker(NodeInfo* info) : info_(info) { + DCHECK(!info->visited); + info->visited = true; + } + ~VisitMarker() { info_->visited = false; } + + private: + NodeInfo* info_; +}; + +// Temporarily sets traversed_loop_initialization_node_. +class LoopInitializationMarker { + public: + explicit LoopInitializationMarker(LoopChoiceNode* node) : node_(node) { + DCHECK(!node_->traversed_loop_initialization_node_); + node_->traversed_loop_initialization_node_ = true; + } + ~LoopInitializationMarker() { + DCHECK(node_->traversed_loop_initialization_node_); + node_->traversed_loop_initialization_node_ = false; + } + LoopInitializationMarker(const LoopInitializationMarker&) = delete; + LoopInitializationMarker& operator=(const LoopInitializationMarker&) = delete; + + private: + LoopChoiceNode* node_; +}; + +// Temporarily decrements min_loop_iterations_. +class IterationDecrementer { + public: + explicit IterationDecrementer(LoopChoiceNode* node) : node_(node) { + DCHECK_GT(node_->min_loop_iterations_, 0); + --node_->min_loop_iterations_; + } + ~IterationDecrementer() { ++node_->min_loop_iterations_; } + IterationDecrementer(const IterationDecrementer&) = delete; + IterationDecrementer& operator=(const IterationDecrementer&) = delete; + + private: + LoopChoiceNode* node_; +}; + +RegExpNode* SeqRegExpNode::FilterOneByte(int depth, RegExpFlags flags) { + if (info()->replacement_calculated) return replacement(); + if (depth < 0) return this; + DCHECK(!info()->visited); + VisitMarker marker(info()); + return FilterSuccessor(depth - 1, flags); +} + +RegExpNode* SeqRegExpNode::FilterSuccessor(int depth, RegExpFlags flags) { + RegExpNode* next = on_success_->FilterOneByte(depth - 1, flags); + if (next == nullptr) return set_replacement(nullptr); + on_success_ = next; + return set_replacement(this); +} + +// We need to check for the following characters: 0x39C 0x3BC 0x178. +bool RangeContainsLatin1Equivalents(CharacterRange range) { + // TODO(dcarney): this could be a lot more efficient. + return range.Contains(0x039C) || range.Contains(0x03BC) || + range.Contains(0x0178); +} + +namespace { + +bool RangesContainLatin1Equivalents(ZoneList<CharacterRange>* ranges) { + for (int i = 0; i < ranges->length(); i++) { + // TODO(dcarney): this could be a lot more efficient. + if (RangeContainsLatin1Equivalents(ranges->at(i))) return true; + } + return false; +} + +} // namespace + +RegExpNode* TextNode::FilterOneByte(int depth, RegExpFlags flags) { + if (info()->replacement_calculated) return replacement(); + if (depth < 0) return this; + DCHECK(!info()->visited); + VisitMarker marker(info()); + int element_count = elements()->length(); + for (int i = 0; i < element_count; i++) { + TextElement elm = elements()->at(i); + if (elm.text_type() == TextElement::ATOM) { + base::Vector<const base::uc16> quarks = elm.atom()->data(); + for (int j = 0; j < quarks.length(); j++) { + base::uc16 c = quarks[j]; + if (IsIgnoreCase(flags)) { + c = unibrow::Latin1::TryConvertToLatin1(c); + } + if (c > unibrow::Latin1::kMaxChar) return set_replacement(nullptr); + // Replace quark in case we converted to Latin-1. + base::uc16* writable_quarks = const_cast<base::uc16*>(quarks.begin()); + writable_quarks[j] = c; + } + } else { + DCHECK(elm.text_type() == TextElement::CLASS_RANGES); + RegExpClassRanges* cr = elm.class_ranges(); + ZoneList<CharacterRange>* ranges = cr->ranges(zone()); + CharacterRange::Canonicalize(ranges); + // Now they are in order so we only need to look at the first. + int range_count = ranges->length(); + if (cr->is_negated()) { + if (range_count != 0 && ranges->at(0).from() == 0 && + ranges->at(0).to() >= String::kMaxOneByteCharCode) { + // This will be handled in a later filter. + if (IsIgnoreCase(flags) && RangesContainLatin1Equivalents(ranges)) { + continue; + } + return set_replacement(nullptr); + } + } else { + if (range_count == 0 || + ranges->at(0).from() > String::kMaxOneByteCharCode) { + // This will be handled in a later filter. + if (IsIgnoreCase(flags) && RangesContainLatin1Equivalents(ranges)) { + continue; + } + return set_replacement(nullptr); + } + } + } + } + return FilterSuccessor(depth - 1, flags); +} + +RegExpNode* LoopChoiceNode::FilterOneByte(int depth, RegExpFlags flags) { + if (info()->replacement_calculated) return replacement(); + if (depth < 0) return this; + if (info()->visited) return this; + { + VisitMarker marker(info()); + + RegExpNode* continue_replacement = + continue_node_->FilterOneByte(depth - 1, flags); + // If we can't continue after the loop then there is no sense in doing the + // loop. + if (continue_replacement == nullptr) return set_replacement(nullptr); + } + + return ChoiceNode::FilterOneByte(depth - 1, flags); +} + +RegExpNode* ChoiceNode::FilterOneByte(int depth, RegExpFlags flags) { + if (info()->replacement_calculated) return replacement(); + if (depth < 0) return this; + if (info()->visited) return this; + VisitMarker marker(info()); + int choice_count = alternatives_->length(); + + for (int i = 0; i < choice_count; i++) { + GuardedAlternative alternative = alternatives_->at(i); + if (alternative.guards() != nullptr && + alternative.guards()->length() != 0) { + set_replacement(this); + return this; + } + } + + int surviving = 0; + RegExpNode* survivor = nullptr; + for (int i = 0; i < choice_count; i++) { + GuardedAlternative alternative = alternatives_->at(i); + RegExpNode* replacement = + alternative.node()->FilterOneByte(depth - 1, flags); + DCHECK(replacement != this); // No missing EMPTY_MATCH_CHECK. + if (replacement != nullptr) { + alternatives_->at(i).set_node(replacement); + surviving++; + survivor = replacement; + } + } + if (surviving < 2) return set_replacement(survivor); + + set_replacement(this); + if (surviving == choice_count) { + return this; + } + // Only some of the nodes survived the filtering. We need to rebuild the + // alternatives list. + ZoneList<GuardedAlternative>* new_alternatives = + zone()->New<ZoneList<GuardedAlternative>>(surviving, zone()); + for (int i = 0; i < choice_count; i++) { + RegExpNode* replacement = + alternatives_->at(i).node()->FilterOneByte(depth - 1, flags); + if (replacement != nullptr) { + alternatives_->at(i).set_node(replacement); + new_alternatives->Add(alternatives_->at(i), zone()); + } + } + alternatives_ = new_alternatives; + return this; +} + +RegExpNode* NegativeLookaroundChoiceNode::FilterOneByte(int depth, + RegExpFlags flags) { + if (info()->replacement_calculated) return replacement(); + if (depth < 0) return this; + if (info()->visited) return this; + VisitMarker marker(info()); + // Alternative 0 is the negative lookahead, alternative 1 is what comes + // afterwards. + RegExpNode* node = continue_node(); + RegExpNode* replacement = node->FilterOneByte(depth - 1, flags); + if (replacement == nullptr) return set_replacement(nullptr); + alternatives_->at(kContinueIndex).set_node(replacement); + + RegExpNode* neg_node = lookaround_node(); + RegExpNode* neg_replacement = neg_node->FilterOneByte(depth - 1, flags); + // If the negative lookahead is always going to fail then + // we don't need to check it. + if (neg_replacement == nullptr) return set_replacement(replacement); + alternatives_->at(kLookaroundIndex).set_node(neg_replacement); + return set_replacement(this); +} + +void LoopChoiceNode::GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start) { + if (body_can_be_zero_length_ || info()->visited) return; + not_at_start = not_at_start || this->not_at_start(); + DCHECK_EQ(alternatives_->length(), 2); // There's just loop and continue. + if (traversed_loop_initialization_node_ && min_loop_iterations_ > 0 && + loop_node_->EatsAtLeast(not_at_start) > + continue_node_->EatsAtLeast(true)) { + // Loop body is guaranteed to execute at least once, and consume characters + // when it does, meaning the only possible quick checks from this point + // begin with the loop body. We may recursively visit this LoopChoiceNode, + // but we temporarily decrease its minimum iteration counter so we know when + // to check the continue case. + IterationDecrementer next_iteration(this); + loop_node_->GetQuickCheckDetails(details, compiler, characters_filled_in, + not_at_start); + } else { + // Might not consume anything in the loop body, so treat it like a normal + // ChoiceNode (and don't recursively visit this node again). + VisitMarker marker(info()); + ChoiceNode::GetQuickCheckDetails(details, compiler, characters_filled_in, + not_at_start); + } +} + +void LoopChoiceNode::GetQuickCheckDetailsFromLoopEntry( + QuickCheckDetails* details, RegExpCompiler* compiler, + int characters_filled_in, bool not_at_start) { + if (traversed_loop_initialization_node_) { + // We already entered this loop once, exited via its continuation node, and + // followed an outer loop's back-edge to before the loop entry point. We + // could try to reset the minimum iteration count to its starting value at + // this point, but that seems like more trouble than it's worth. It's safe + // to keep going with the current (possibly reduced) minimum iteration + // count. + GetQuickCheckDetails(details, compiler, characters_filled_in, not_at_start); + } else { + // We are entering a loop via its counter initialization action, meaning we + // are guaranteed to run the loop body at least some minimum number of times + // before running the continuation node. Set a flag so that this node knows + // (now and any times we visit it again recursively) that it was entered + // from the top. + LoopInitializationMarker marker(this); + GetQuickCheckDetails(details, compiler, characters_filled_in, not_at_start); + } +} + +void LoopChoiceNode::FillInBMInfo(Isolate* isolate, int offset, int budget, + BoyerMooreLookahead* bm, bool not_at_start) { + if (body_can_be_zero_length_ || budget <= 0) { + bm->SetRest(offset); + SaveBMInfo(bm, not_at_start, offset); + return; + } + ChoiceNode::FillInBMInfo(isolate, offset, budget - 1, bm, not_at_start); + SaveBMInfo(bm, not_at_start, offset); +} + +void ChoiceNode::GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start) { + not_at_start = (not_at_start || not_at_start_); + int choice_count = alternatives_->length(); + DCHECK_LT(0, choice_count); + alternatives_->at(0).node()->GetQuickCheckDetails( + details, compiler, characters_filled_in, not_at_start); + for (int i = 1; i < choice_count; i++) { + QuickCheckDetails new_details(details->characters()); + RegExpNode* node = alternatives_->at(i).node(); + node->GetQuickCheckDetails(&new_details, compiler, characters_filled_in, + not_at_start); + // Here we merge the quick match details of the two branches. + details->Merge(&new_details, characters_filled_in); + } +} + +namespace { + +// Check for [0-9A-Z_a-z]. +void EmitWordCheck(RegExpMacroAssembler* assembler, Label* word, + Label* non_word, bool fall_through_on_word) { + if (assembler->CheckSpecialClassRanges( + fall_through_on_word ? StandardCharacterSet::kWord + : StandardCharacterSet::kNotWord, + fall_through_on_word ? non_word : word)) { + // Optimized implementation available. + return; + } + assembler->CheckCharacterGT('z', non_word); + assembler->CheckCharacterLT('0', non_word); + assembler->CheckCharacterGT('a' - 1, word); + assembler->CheckCharacterLT('9' + 1, word); + assembler->CheckCharacterLT('A', non_word); + assembler->CheckCharacterLT('Z' + 1, word); + if (fall_through_on_word) { + assembler->CheckNotCharacter('_', non_word); + } else { + assembler->CheckCharacter('_', word); + } +} + +// Emit the code to check for a ^ in multiline mode (1-character lookbehind +// that matches newline or the start of input). +void EmitHat(RegExpCompiler* compiler, RegExpNode* on_success, Trace* trace) { + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + + // We will load the previous character into the current character register. + Trace new_trace(*trace); + new_trace.InvalidateCurrentCharacter(); + + // A positive (> 0) cp_offset means we've already successfully matched a + // non-empty-width part of the pattern, and thus cannot be at or before the + // start of the subject string. We can thus skip both at-start and + // bounds-checks when loading the one-character lookbehind. + const bool may_be_at_or_before_subject_string_start = + new_trace.cp_offset() <= 0; + + Label ok; + if (may_be_at_or_before_subject_string_start) { + // The start of input counts as a newline in this context, so skip to ok if + // we are at the start. + assembler->CheckAtStart(new_trace.cp_offset(), &ok); + } + + // If we've already checked that we are not at the start of input, it's okay + // to load the previous character without bounds checks. + const bool can_skip_bounds_check = !may_be_at_or_before_subject_string_start; + assembler->LoadCurrentCharacter(new_trace.cp_offset() - 1, + new_trace.backtrack(), can_skip_bounds_check); + if (!assembler->CheckSpecialClassRanges(StandardCharacterSet::kLineTerminator, + new_trace.backtrack())) { + // Newline means \n, \r, 0x2028 or 0x2029. + if (!compiler->one_byte()) { + assembler->CheckCharacterAfterAnd(0x2028, 0xFFFE, &ok); + } + assembler->CheckCharacter('\n', &ok); + assembler->CheckNotCharacter('\r', new_trace.backtrack()); + } + assembler->Bind(&ok); + on_success->Emit(compiler, &new_trace); +} + +} // namespace + +// Emit the code to handle \b and \B (word-boundary or non-word-boundary). +void AssertionNode::EmitBoundaryCheck(RegExpCompiler* compiler, Trace* trace) { + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + Isolate* isolate = assembler->isolate(); + Trace::TriBool next_is_word_character = Trace::UNKNOWN; + bool not_at_start = (trace->at_start() == Trace::FALSE_VALUE); + BoyerMooreLookahead* lookahead = bm_info(not_at_start); + if (lookahead == nullptr) { + int eats_at_least = + std::min(kMaxLookaheadForBoyerMoore, EatsAtLeast(not_at_start)); + if (eats_at_least >= 1) { + BoyerMooreLookahead* bm = + zone()->New<BoyerMooreLookahead>(eats_at_least, compiler, zone()); + FillInBMInfo(isolate, 0, kRecursionBudget, bm, not_at_start); + if (bm->at(0)->is_non_word()) next_is_word_character = Trace::FALSE_VALUE; + if (bm->at(0)->is_word()) next_is_word_character = Trace::TRUE_VALUE; + } + } else { + if (lookahead->at(0)->is_non_word()) + next_is_word_character = Trace::FALSE_VALUE; + if (lookahead->at(0)->is_word()) next_is_word_character = Trace::TRUE_VALUE; + } + bool at_boundary = (assertion_type_ == AssertionNode::AT_BOUNDARY); + if (next_is_word_character == Trace::UNKNOWN) { + Label before_non_word; + Label before_word; + if (trace->characters_preloaded() != 1) { + assembler->LoadCurrentCharacter(trace->cp_offset(), &before_non_word); + } + // Fall through on non-word. + EmitWordCheck(assembler, &before_word, &before_non_word, false); + // Next character is not a word character. + assembler->Bind(&before_non_word); + Label ok; + BacktrackIfPrevious(compiler, trace, at_boundary ? kIsNonWord : kIsWord); + assembler->GoTo(&ok); + + assembler->Bind(&before_word); + BacktrackIfPrevious(compiler, trace, at_boundary ? kIsWord : kIsNonWord); + assembler->Bind(&ok); + } else if (next_is_word_character == Trace::TRUE_VALUE) { + BacktrackIfPrevious(compiler, trace, at_boundary ? kIsWord : kIsNonWord); + } else { + DCHECK(next_is_word_character == Trace::FALSE_VALUE); + BacktrackIfPrevious(compiler, trace, at_boundary ? kIsNonWord : kIsWord); + } +} + +void AssertionNode::BacktrackIfPrevious( + RegExpCompiler* compiler, Trace* trace, + AssertionNode::IfPrevious backtrack_if_previous) { + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + Trace new_trace(*trace); + new_trace.InvalidateCurrentCharacter(); + + Label fall_through; + Label* non_word = backtrack_if_previous == kIsNonWord ? new_trace.backtrack() + : &fall_through; + Label* word = backtrack_if_previous == kIsNonWord ? &fall_through + : new_trace.backtrack(); + + // A positive (> 0) cp_offset means we've already successfully matched a + // non-empty-width part of the pattern, and thus cannot be at or before the + // start of the subject string. We can thus skip both at-start and + // bounds-checks when loading the one-character lookbehind. + const bool may_be_at_or_before_subject_string_start = + new_trace.cp_offset() <= 0; + + if (may_be_at_or_before_subject_string_start) { + // The start of input counts as a non-word character, so the question is + // decided if we are at the start. + assembler->CheckAtStart(new_trace.cp_offset(), non_word); + } + + // If we've already checked that we are not at the start of input, it's okay + // to load the previous character without bounds checks. + const bool can_skip_bounds_check = !may_be_at_or_before_subject_string_start; + assembler->LoadCurrentCharacter(new_trace.cp_offset() - 1, non_word, + can_skip_bounds_check); + EmitWordCheck(assembler, word, non_word, backtrack_if_previous == kIsNonWord); + + assembler->Bind(&fall_through); + on_success()->Emit(compiler, &new_trace); +} + +void AssertionNode::GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int filled_in, bool not_at_start) { + if (assertion_type_ == AT_START && not_at_start) { + details->set_cannot_match(); + return; + } + return on_success()->GetQuickCheckDetails(details, compiler, filled_in, + not_at_start); +} + +void AssertionNode::Emit(RegExpCompiler* compiler, Trace* trace) { + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + switch (assertion_type_) { + case AT_END: { + Label ok; + assembler->CheckPosition(trace->cp_offset(), &ok); + assembler->GoTo(trace->backtrack()); + assembler->Bind(&ok); + break; + } + case AT_START: { + if (trace->at_start() == Trace::FALSE_VALUE) { + assembler->GoTo(trace->backtrack()); + return; + } + if (trace->at_start() == Trace::UNKNOWN) { + assembler->CheckNotAtStart(trace->cp_offset(), trace->backtrack()); + Trace at_start_trace = *trace; + at_start_trace.set_at_start(Trace::TRUE_VALUE); + on_success()->Emit(compiler, &at_start_trace); + return; + } + } break; + case AFTER_NEWLINE: + EmitHat(compiler, on_success(), trace); + return; + case AT_BOUNDARY: + case AT_NON_BOUNDARY: { + EmitBoundaryCheck(compiler, trace); + return; + } + } + on_success()->Emit(compiler, trace); +} + +namespace { + +bool DeterminedAlready(QuickCheckDetails* quick_check, int offset) { + if (quick_check == nullptr) return false; + if (offset >= quick_check->characters()) return false; + return quick_check->positions(offset)->determines_perfectly; +} + +void UpdateBoundsCheck(int index, int* checked_up_to) { + if (index > *checked_up_to) { + *checked_up_to = index; + } +} + +} // namespace + +// We call this repeatedly to generate code for each pass over the text node. +// The passes are in increasing order of difficulty because we hope one +// of the first passes will fail in which case we are saved the work of the +// later passes. for example for the case independent regexp /%[asdfghjkl]a/ +// we will check the '%' in the first pass, the case independent 'a' in the +// second pass and the character class in the last pass. +// +// The passes are done from right to left, so for example to test for /bar/ +// we will first test for an 'r' with offset 2, then an 'a' with offset 1 +// and then a 'b' with offset 0. This means we can avoid the end-of-input +// bounds check most of the time. In the example we only need to check for +// end-of-input when loading the putative 'r'. +// +// A slight complication involves the fact that the first character may already +// be fetched into a register by the previous node. In this case we want to +// do the test for that character first. We do this in separate passes. The +// 'preloaded' argument indicates that we are doing such a 'pass'. If such a +// pass has been performed then subsequent passes will have true in +// first_element_checked to indicate that that character does not need to be +// checked again. +// +// In addition to all this we are passed a Trace, which can +// contain an AlternativeGeneration object. In this AlternativeGeneration +// object we can see details of any quick check that was already passed in +// order to get to the code we are now generating. The quick check can involve +// loading characters, which means we do not need to recheck the bounds +// up to the limit the quick check already checked. In addition the quick +// check can have involved a mask and compare operation which may simplify +// or obviate the need for further checks at some character positions. +void TextNode::TextEmitPass(RegExpCompiler* compiler, TextEmitPassType pass, + bool preloaded, Trace* trace, + bool first_element_checked, int* checked_up_to) { + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + Isolate* isolate = assembler->isolate(); + bool one_byte = compiler->one_byte(); + Label* backtrack = trace->backtrack(); + QuickCheckDetails* quick_check = trace->quick_check_performed(); + int element_count = elements()->length(); + int backward_offset = read_backward() ? -Length() : 0; + for (int i = preloaded ? 0 : element_count - 1; i >= 0; i--) { + TextElement elm = elements()->at(i); + int cp_offset = trace->cp_offset() + elm.cp_offset() + backward_offset; + if (elm.text_type() == TextElement::ATOM) { + if (SkipPass(pass, IsIgnoreCase(compiler->flags()))) continue; + base::Vector<const base::uc16> quarks = elm.atom()->data(); + for (int j = preloaded ? 0 : quarks.length() - 1; j >= 0; j--) { + if (first_element_checked && i == 0 && j == 0) continue; + if (DeterminedAlready(quick_check, elm.cp_offset() + j)) continue; + base::uc16 quark = quarks[j]; + if (IsIgnoreCase(compiler->flags())) { + // Everywhere else we assume that a non-Latin-1 character cannot match + // a Latin-1 character. Avoid the cases where this is assumption is + // invalid by using the Latin1 equivalent instead. + quark = unibrow::Latin1::TryConvertToLatin1(quark); + } + bool needs_bounds_check = + *checked_up_to < cp_offset + j || read_backward(); + bool bounds_checked = false; + switch (pass) { + case NON_LATIN1_MATCH: + DCHECK(one_byte); + if (quark > String::kMaxOneByteCharCode) { + assembler->GoTo(backtrack); + return; + } + break; + case NON_LETTER_CHARACTER_MATCH: + bounds_checked = + EmitAtomNonLetter(isolate, compiler, quark, backtrack, + cp_offset + j, needs_bounds_check, preloaded); + break; + case SIMPLE_CHARACTER_MATCH: + bounds_checked = EmitSimpleCharacter(isolate, compiler, quark, + backtrack, cp_offset + j, + needs_bounds_check, preloaded); + break; + case CASE_CHARACTER_MATCH: + bounds_checked = + EmitAtomLetter(isolate, compiler, quark, backtrack, + cp_offset + j, needs_bounds_check, preloaded); + break; + default: + break; + } + if (bounds_checked) UpdateBoundsCheck(cp_offset + j, checked_up_to); + } + } else { + DCHECK_EQ(TextElement::CLASS_RANGES, elm.text_type()); + if (pass == CHARACTER_CLASS_MATCH) { + if (first_element_checked && i == 0) continue; + if (DeterminedAlready(quick_check, elm.cp_offset())) continue; + RegExpClassRanges* cr = elm.class_ranges(); + bool bounds_check = *checked_up_to < cp_offset || read_backward(); + EmitClassRanges(assembler, cr, one_byte, backtrack, cp_offset, + bounds_check, preloaded, zone()); + UpdateBoundsCheck(cp_offset, checked_up_to); + } + } + } +} + +int TextNode::Length() { + TextElement elm = elements()->last(); + DCHECK_LE(0, elm.cp_offset()); + return elm.cp_offset() + elm.length(); +} + +bool TextNode::SkipPass(TextEmitPassType pass, bool ignore_case) { + if (ignore_case) { + return pass == SIMPLE_CHARACTER_MATCH; + } else { + return pass == NON_LETTER_CHARACTER_MATCH || pass == CASE_CHARACTER_MATCH; + } +} + +TextNode* TextNode::CreateForCharacterRanges(Zone* zone, + ZoneList<CharacterRange>* ranges, + bool read_backward, + RegExpNode* on_success) { + DCHECK_NOT_NULL(ranges); + // TODO(jgruber): There's no fundamental need to create this + // RegExpClassRanges; we could refactor to avoid the allocation. + return zone->New<TextNode>(zone->New<RegExpClassRanges>(zone, ranges), + read_backward, on_success); +} + +TextNode* TextNode::CreateForSurrogatePair( + Zone* zone, CharacterRange lead, ZoneList<CharacterRange>* trail_ranges, + bool read_backward, RegExpNode* on_success) { + ZoneList<CharacterRange>* lead_ranges = CharacterRange::List(zone, lead); + ZoneList<TextElement>* elms = zone->New<ZoneList<TextElement>>(2, zone); + elms->Add( + TextElement::ClassRanges(zone->New<RegExpClassRanges>(zone, lead_ranges)), + zone); + elms->Add(TextElement::ClassRanges( + zone->New<RegExpClassRanges>(zone, trail_ranges)), + zone); + return zone->New<TextNode>(elms, read_backward, on_success); +} + +TextNode* TextNode::CreateForSurrogatePair( + Zone* zone, ZoneList<CharacterRange>* lead_ranges, CharacterRange trail, + bool read_backward, RegExpNode* on_success) { + ZoneList<CharacterRange>* trail_ranges = CharacterRange::List(zone, trail); + ZoneList<TextElement>* elms = zone->New<ZoneList<TextElement>>(2, zone); + elms->Add( + TextElement::ClassRanges(zone->New<RegExpClassRanges>(zone, lead_ranges)), + zone); + elms->Add(TextElement::ClassRanges( + zone->New<RegExpClassRanges>(zone, trail_ranges)), + zone); + return zone->New<TextNode>(elms, read_backward, on_success); +} + +// This generates the code to match a text node. A text node can contain +// straight character sequences (possibly to be matched in a case-independent +// way) and character classes. For efficiency we do not do this in a single +// pass from left to right. Instead we pass over the text node several times, +// emitting code for some character positions every time. See the comment on +// TextEmitPass for details. +void TextNode::Emit(RegExpCompiler* compiler, Trace* trace) { + LimitResult limit_result = LimitVersions(compiler, trace); + if (limit_result == DONE) return; + DCHECK(limit_result == CONTINUE); + + if (trace->cp_offset() + Length() > RegExpMacroAssembler::kMaxCPOffset) { + compiler->SetRegExpTooBig(); + return; + } + + if (compiler->one_byte()) { + int dummy = 0; + TextEmitPass(compiler, NON_LATIN1_MATCH, false, trace, false, &dummy); + } + + bool first_elt_done = false; + int bound_checked_to = trace->cp_offset() - 1; + bound_checked_to += trace->bound_checked_up_to(); + + // If a character is preloaded into the current character register then + // check that now. + if (trace->characters_preloaded() == 1) { + for (int pass = kFirstRealPass; pass <= kLastPass; pass++) { + TextEmitPass(compiler, static_cast<TextEmitPassType>(pass), true, trace, + false, &bound_checked_to); + } + first_elt_done = true; + } + + for (int pass = kFirstRealPass; pass <= kLastPass; pass++) { + TextEmitPass(compiler, static_cast<TextEmitPassType>(pass), false, trace, + first_elt_done, &bound_checked_to); + } + + Trace successor_trace(*trace); + // If we advance backward, we may end up at the start. + successor_trace.AdvanceCurrentPositionInTrace( + read_backward() ? -Length() : Length(), compiler); + successor_trace.set_at_start(read_backward() ? Trace::UNKNOWN + : Trace::FALSE_VALUE); + RecursionCheck rc(compiler); + on_success()->Emit(compiler, &successor_trace); +} + +void Trace::InvalidateCurrentCharacter() { characters_preloaded_ = 0; } + +void Trace::AdvanceCurrentPositionInTrace(int by, RegExpCompiler* compiler) { + // We don't have an instruction for shifting the current character register + // down or for using a shifted value for anything so lets just forget that + // we preloaded any characters into it. + characters_preloaded_ = 0; + // Adjust the offsets of the quick check performed information. This + // information is used to find out what we already determined about the + // characters by means of mask and compare. + quick_check_performed_.Advance(by, compiler->one_byte()); + cp_offset_ += by; + if (cp_offset_ > RegExpMacroAssembler::kMaxCPOffset) { + compiler->SetRegExpTooBig(); + cp_offset_ = 0; + } + bound_checked_up_to_ = std::max(0, bound_checked_up_to_ - by); +} + +void TextNode::MakeCaseIndependent(Isolate* isolate, bool is_one_byte, + RegExpFlags flags) { + if (!IsIgnoreCase(flags)) return; +#ifdef V8_INTL_SUPPORT + if (NeedsUnicodeCaseEquivalents(flags)) return; +#endif + + int element_count = elements()->length(); + for (int i = 0; i < element_count; i++) { + TextElement elm = elements()->at(i); + if (elm.text_type() == TextElement::CLASS_RANGES) { + RegExpClassRanges* cr = elm.class_ranges(); + // None of the standard character classes is different in the case + // independent case and it slows us down if we don't know that. + if (cr->is_standard(zone())) continue; + ZoneList<CharacterRange>* ranges = cr->ranges(zone()); + CharacterRange::AddCaseEquivalents(isolate, zone(), ranges, is_one_byte); + } + } +} + +int TextNode::GreedyLoopTextLength() { return Length(); } + +RegExpNode* TextNode::GetSuccessorOfOmnivorousTextNode( + RegExpCompiler* compiler) { + if (read_backward()) return nullptr; + if (elements()->length() != 1) return nullptr; + TextElement elm = elements()->at(0); + if (elm.text_type() != TextElement::CLASS_RANGES) return nullptr; + RegExpClassRanges* node = elm.class_ranges(); + ZoneList<CharacterRange>* ranges = node->ranges(zone()); + CharacterRange::Canonicalize(ranges); + if (node->is_negated()) { + return ranges->length() == 0 ? on_success() : nullptr; + } + if (ranges->length() != 1) return nullptr; + const base::uc32 max_char = MaxCodeUnit(compiler->one_byte()); + return ranges->at(0).IsEverything(max_char) ? on_success() : nullptr; +} + +// Finds the fixed match length of a sequence of nodes that goes from +// this alternative and back to this choice node. If there are variable +// length nodes or other complications in the way then return a sentinel +// value indicating that a greedy loop cannot be constructed. +int ChoiceNode::GreedyLoopTextLengthForAlternative( + GuardedAlternative* alternative) { + int length = 0; + RegExpNode* node = alternative->node(); + // Later we will generate code for all these text nodes using recursion + // so we have to limit the max number. + int recursion_depth = 0; + while (node != this) { + if (recursion_depth++ > RegExpCompiler::kMaxRecursion) { + return kNodeIsTooComplexForGreedyLoops; + } + int node_length = node->GreedyLoopTextLength(); + if (node_length == kNodeIsTooComplexForGreedyLoops) { + return kNodeIsTooComplexForGreedyLoops; + } + length += node_length; + SeqRegExpNode* seq_node = static_cast<SeqRegExpNode*>(node); + node = seq_node->on_success(); + } + if (read_backward()) { + length = -length; + } + // Check that we can jump by the whole text length. If not, return sentinel + // to indicate the we can't construct a greedy loop. + if (length < RegExpMacroAssembler::kMinCPOffset || + length > RegExpMacroAssembler::kMaxCPOffset) { + return kNodeIsTooComplexForGreedyLoops; + } + return length; +} + +void LoopChoiceNode::AddLoopAlternative(GuardedAlternative alt) { + DCHECK_NULL(loop_node_); + AddAlternative(alt); + loop_node_ = alt.node(); +} + +void LoopChoiceNode::AddContinueAlternative(GuardedAlternative alt) { + DCHECK_NULL(continue_node_); + AddAlternative(alt); + continue_node_ = alt.node(); +} + +void LoopChoiceNode::Emit(RegExpCompiler* compiler, Trace* trace) { + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + if (trace->stop_node() == this) { + // Back edge of greedy optimized loop node graph. + int text_length = + GreedyLoopTextLengthForAlternative(&(alternatives_->at(0))); + DCHECK_NE(kNodeIsTooComplexForGreedyLoops, text_length); + // Update the counter-based backtracking info on the stack. This is an + // optimization for greedy loops (see below). + DCHECK(trace->cp_offset() == text_length); + macro_assembler->AdvanceCurrentPosition(text_length); + macro_assembler->GoTo(trace->loop_label()); + return; + } + DCHECK_NULL(trace->stop_node()); + if (!trace->is_trivial()) { + trace->Flush(compiler, this); + return; + } + ChoiceNode::Emit(compiler, trace); +} + +int ChoiceNode::CalculatePreloadCharacters(RegExpCompiler* compiler, + int eats_at_least) { + int preload_characters = std::min(4, eats_at_least); + DCHECK_LE(preload_characters, 4); + if (compiler->macro_assembler()->CanReadUnaligned()) { + bool one_byte = compiler->one_byte(); + if (one_byte) { + // We can't preload 3 characters because there is no machine instruction + // to do that. We can't just load 4 because we could be reading + // beyond the end of the string, which could cause a memory fault. + if (preload_characters == 3) preload_characters = 2; + } else { + if (preload_characters > 2) preload_characters = 2; + } + } else { + if (preload_characters > 1) preload_characters = 1; + } + return preload_characters; +} + +// This class is used when generating the alternatives in a choice node. It +// records the way the alternative is being code generated. +class AlternativeGeneration : public Malloced { + public: + AlternativeGeneration() + : possible_success(), + expects_preload(false), + after(), + quick_check_details() {} + Label possible_success; + bool expects_preload; + Label after; + QuickCheckDetails quick_check_details; +}; + +// Creates a list of AlternativeGenerations. If the list has a reasonable +// size then it is on the stack, otherwise the excess is on the heap. +class AlternativeGenerationList { + public: + AlternativeGenerationList(int count, Zone* zone) : alt_gens_(count, zone) { + for (int i = 0; i < count && i < kAFew; i++) { + alt_gens_.Add(a_few_alt_gens_ + i, zone); + } + for (int i = kAFew; i < count; i++) { + alt_gens_.Add(new AlternativeGeneration(), zone); + } + } + ~AlternativeGenerationList() { + for (int i = kAFew; i < alt_gens_.length(); i++) { + delete alt_gens_[i]; + alt_gens_[i] = nullptr; + } + } + + AlternativeGeneration* at(int i) { return alt_gens_[i]; } + + private: + static const int kAFew = 10; + ZoneList<AlternativeGeneration*> alt_gens_; + AlternativeGeneration a_few_alt_gens_[kAFew]; +}; + +void BoyerMoorePositionInfo::Set(int character) { + SetInterval(Interval(character, character)); +} + +namespace { + +ContainedInLattice AddRange(ContainedInLattice containment, const int* ranges, + int ranges_length, Interval new_range) { + DCHECK_EQ(1, ranges_length & 1); + DCHECK_EQ(String::kMaxCodePoint + 1, ranges[ranges_length - 1]); + if (containment == kLatticeUnknown) return containment; + bool inside = false; + int last = 0; + for (int i = 0; i < ranges_length; inside = !inside, last = ranges[i], i++) { + // Consider the range from last to ranges[i]. + // We haven't got to the new range yet. + if (ranges[i] <= new_range.from()) continue; + // New range is wholly inside last-ranges[i]. Note that new_range.to() is + // inclusive, but the values in ranges are not. + if (last <= new_range.from() && new_range.to() < ranges[i]) { + return Combine(containment, inside ? kLatticeIn : kLatticeOut); + } + return kLatticeUnknown; + } + return containment; +} + +int BitsetFirstSetBit(BoyerMoorePositionInfo::Bitset bitset) { + static_assert(BoyerMoorePositionInfo::kMapSize == + 2 * kInt64Size * kBitsPerByte); + + // Slight fiddling is needed here, since the bitset is of length 128 while + // CountTrailingZeros requires an integral type and std::bitset can only + // convert to unsigned long long. So we handle the most- and least-significant + // bits separately. + + { + static constexpr BoyerMoorePositionInfo::Bitset mask(~uint64_t{0}); + BoyerMoorePositionInfo::Bitset masked_bitset = bitset & mask; + static_assert(kInt64Size >= sizeof(decltype(masked_bitset.to_ullong()))); + uint64_t lsb = masked_bitset.to_ullong(); + if (lsb != 0) return base::bits::CountTrailingZeros(lsb); + } + + { + BoyerMoorePositionInfo::Bitset masked_bitset = bitset >> 64; + uint64_t msb = masked_bitset.to_ullong(); + if (msb != 0) return 64 + base::bits::CountTrailingZeros(msb); + } + + return -1; +} + +} // namespace + +void BoyerMoorePositionInfo::SetInterval(const Interval& interval) { + w_ = AddRange(w_, kWordRanges, kWordRangeCount, interval); + + if (interval.size() >= kMapSize) { + map_count_ = kMapSize; + map_.set(); + return; + } + + for (int i = interval.from(); i <= interval.to(); i++) { + int mod_character = (i & kMask); + if (!map_[mod_character]) { + map_count_++; + map_.set(mod_character); + } + if (map_count_ == kMapSize) return; + } +} + +void BoyerMoorePositionInfo::SetAll() { + w_ = kLatticeUnknown; + if (map_count_ != kMapSize) { + map_count_ = kMapSize; + map_.set(); + } +} + +BoyerMooreLookahead::BoyerMooreLookahead(int length, RegExpCompiler* compiler, + Zone* zone) + : length_(length), + compiler_(compiler), + max_char_(MaxCodeUnit(compiler->one_byte())) { + bitmaps_ = zone->New<ZoneList<BoyerMoorePositionInfo*>>(length, zone); + for (int i = 0; i < length; i++) { + bitmaps_->Add(zone->New<BoyerMoorePositionInfo>(), zone); + } +} + +// Find the longest range of lookahead that has the fewest number of different +// characters that can occur at a given position. Since we are optimizing two +// different parameters at once this is a tradeoff. +bool BoyerMooreLookahead::FindWorthwhileInterval(int* from, int* to) { + int biggest_points = 0; + // If more than 32 characters out of 128 can occur it is unlikely that we can + // be lucky enough to step forwards much of the time. + const int kMaxMax = 32; + for (int max_number_of_chars = 4; max_number_of_chars < kMaxMax; + max_number_of_chars *= 2) { + biggest_points = + FindBestInterval(max_number_of_chars, biggest_points, from, to); + } + if (biggest_points == 0) return false; + return true; +} + +// Find the highest-points range between 0 and length_ where the character +// information is not too vague. 'Too vague' means that there are more than +// max_number_of_chars that can occur at this position. Calculates the number +// of points as the product of width-of-the-range and +// probability-of-finding-one-of-the-characters, where the probability is +// calculated using the frequency distribution of the sample subject string. +int BoyerMooreLookahead::FindBestInterval(int max_number_of_chars, + int old_biggest_points, int* from, + int* to) { + int biggest_points = old_biggest_points; + static const int kSize = RegExpMacroAssembler::kTableSize; + for (int i = 0; i < length_;) { + while (i < length_ && Count(i) > max_number_of_chars) i++; + if (i == length_) break; + int remembered_from = i; + + BoyerMoorePositionInfo::Bitset union_bitset; + for (; i < length_ && Count(i) <= max_number_of_chars; i++) { + union_bitset |= bitmaps_->at(i)->raw_bitset(); + } + + int frequency = 0; + + // Iterate only over set bits. + int j; + while ((j = BitsetFirstSetBit(union_bitset)) != -1) { + DCHECK(union_bitset[j]); // Sanity check. + // Add 1 to the frequency to give a small per-character boost for + // the cases where our sampling is not good enough and many + // characters have a frequency of zero. This means the frequency + // can theoretically be up to 2*kSize though we treat it mostly as + // a fraction of kSize. + frequency += compiler_->frequency_collator()->Frequency(j) + 1; + union_bitset.reset(j); + } + + // We use the probability of skipping times the distance we are skipping to + // judge the effectiveness of this. Actually we have a cut-off: By + // dividing by 2 we switch off the skipping if the probability of skipping + // is less than 50%. This is because the multibyte mask-and-compare + // skipping in quickcheck is more likely to do well on this case. + bool in_quickcheck_range = + ((i - remembered_from < 4) || + (compiler_->one_byte() ? remembered_from <= 4 : remembered_from <= 2)); + // Called 'probability' but it is only a rough estimate and can actually + // be outside the 0-kSize range. + int probability = (in_quickcheck_range ? kSize / 2 : kSize) - frequency; + int points = (i - remembered_from) * probability; + if (points > biggest_points) { + *from = remembered_from; + *to = i - 1; + biggest_points = points; + } + } + return biggest_points; +} + +// Take all the characters that will not prevent a successful match if they +// occur in the subject string in the range between min_lookahead and +// max_lookahead (inclusive) measured from the current position. If the +// character at max_lookahead offset is not one of these characters, then we +// can safely skip forwards by the number of characters in the range. +int BoyerMooreLookahead::GetSkipTable(int min_lookahead, int max_lookahead, + Handle<ByteArray> boolean_skip_table) { + const int kSkipArrayEntry = 0; + const int kDontSkipArrayEntry = 1; + + std::memset(boolean_skip_table->GetDataStartAddress(), kSkipArrayEntry, + boolean_skip_table->length()); + + for (int i = max_lookahead; i >= min_lookahead; i--) { + BoyerMoorePositionInfo::Bitset bitset = bitmaps_->at(i)->raw_bitset(); + + // Iterate only over set bits. + int j; + while ((j = BitsetFirstSetBit(bitset)) != -1) { + DCHECK(bitset[j]); // Sanity check. + boolean_skip_table->set(j, kDontSkipArrayEntry); + bitset.reset(j); + } + } + + const int skip = max_lookahead + 1 - min_lookahead; + return skip; +} + +// See comment above on the implementation of GetSkipTable. +void BoyerMooreLookahead::EmitSkipInstructions(RegExpMacroAssembler* masm) { + const int kSize = RegExpMacroAssembler::kTableSize; + + int min_lookahead = 0; + int max_lookahead = 0; + + if (!FindWorthwhileInterval(&min_lookahead, &max_lookahead)) return; + + // Check if we only have a single non-empty position info, and that info + // contains precisely one character. + bool found_single_character = false; + int single_character = 0; + for (int i = max_lookahead; i >= min_lookahead; i--) { + BoyerMoorePositionInfo* map = bitmaps_->at(i); + if (map->map_count() == 0) continue; + + if (found_single_character || map->map_count() > 1) { + found_single_character = false; + break; + } + + DCHECK(!found_single_character); + DCHECK_EQ(map->map_count(), 1); + + found_single_character = true; + single_character = BitsetFirstSetBit(map->raw_bitset()); + + DCHECK_NE(single_character, -1); + } + + int lookahead_width = max_lookahead + 1 - min_lookahead; + + if (found_single_character && lookahead_width == 1 && max_lookahead < 3) { + // The mask-compare can probably handle this better. + return; + } + + if (found_single_character) { + Label cont, again; + masm->Bind(&again); + masm->LoadCurrentCharacter(max_lookahead, &cont, true); + if (max_char_ > kSize) { + masm->CheckCharacterAfterAnd(single_character, + RegExpMacroAssembler::kTableMask, &cont); + } else { + masm->CheckCharacter(single_character, &cont); + } + masm->AdvanceCurrentPosition(lookahead_width); + masm->GoTo(&again); + masm->Bind(&cont); + return; + } + + Factory* factory = masm->isolate()->factory(); + Handle<ByteArray> boolean_skip_table = + factory->NewByteArray(kSize, AllocationType::kOld); + int skip_distance = + GetSkipTable(min_lookahead, max_lookahead, boolean_skip_table); + DCHECK_NE(0, skip_distance); + + Label cont, again; + masm->Bind(&again); + masm->LoadCurrentCharacter(max_lookahead, &cont, true); + masm->CheckBitInTable(boolean_skip_table, &cont); + masm->AdvanceCurrentPosition(skip_distance); + masm->GoTo(&again); + masm->Bind(&cont); +} + +/* Code generation for choice nodes. + * + * We generate quick checks that do a mask and compare to eliminate a + * choice. If the quick check succeeds then it jumps to the continuation to + * do slow checks and check subsequent nodes. If it fails (the common case) + * it falls through to the next choice. + * + * Here is the desired flow graph. Nodes directly below each other imply + * fallthrough. Alternatives 1 and 2 have quick checks. Alternative + * 3 doesn't have a quick check so we have to call the slow check. + * Nodes are marked Qn for quick checks and Sn for slow checks. The entire + * regexp continuation is generated directly after the Sn node, up to the + * next GoTo if we decide to reuse some already generated code. Some + * nodes expect preload_characters to be preloaded into the current + * character register. R nodes do this preloading. Vertices are marked + * F for failures and S for success (possible success in the case of quick + * nodes). L, V, < and > are used as arrow heads. + * + * ----------> R + * | + * V + * Q1 -----> S1 + * | S / + * F| / + * | F/ + * | / + * | R + * | / + * V L + * Q2 -----> S2 + * | S / + * F| / + * | F/ + * | / + * | R + * | / + * V L + * S3 + * | + * F| + * | + * R + * | + * backtrack V + * <----------Q4 + * \ F | + * \ |S + * \ F V + * \-----S4 + * + * For greedy loops we push the current position, then generate the code that + * eats the input specially in EmitGreedyLoop. The other choice (the + * continuation) is generated by the normal code in EmitChoices, and steps back + * in the input to the starting position when it fails to match. The loop code + * looks like this (U is the unwind code that steps back in the greedy loop). + * + * _____ + * / \ + * V | + * ----------> S1 | + * /| | + * / |S | + * F/ \_____/ + * / + * |<----- + * | \ + * V |S + * Q2 ---> U----->backtrack + * | F / + * S| / + * V F / + * S2--/ + */ + +GreedyLoopState::GreedyLoopState(bool not_at_start) { + counter_backtrack_trace_.set_backtrack(&label_); + if (not_at_start) counter_backtrack_trace_.set_at_start(Trace::FALSE_VALUE); +} + +void ChoiceNode::AssertGuardsMentionRegisters(Trace* trace) { +#ifdef DEBUG + int choice_count = alternatives_->length(); + for (int i = 0; i < choice_count - 1; i++) { + GuardedAlternative alternative = alternatives_->at(i); + ZoneList<Guard*>* guards = alternative.guards(); + int guard_count = (guards == nullptr) ? 0 : guards->length(); + for (int j = 0; j < guard_count; j++) { + DCHECK(!trace->mentions_reg(guards->at(j)->reg())); + } + } +#endif +} + +void ChoiceNode::SetUpPreLoad(RegExpCompiler* compiler, Trace* current_trace, + PreloadState* state) { + if (state->eats_at_least_ == PreloadState::kEatsAtLeastNotYetInitialized) { + // Save some time by looking at most one machine word ahead. + state->eats_at_least_ = + EatsAtLeast(current_trace->at_start() == Trace::FALSE_VALUE); + } + state->preload_characters_ = + CalculatePreloadCharacters(compiler, state->eats_at_least_); + + state->preload_is_current_ = + (current_trace->characters_preloaded() == state->preload_characters_); + state->preload_has_checked_bounds_ = state->preload_is_current_; +} + +void ChoiceNode::Emit(RegExpCompiler* compiler, Trace* trace) { + int choice_count = alternatives_->length(); + + if (choice_count == 1 && alternatives_->at(0).guards() == nullptr) { + alternatives_->at(0).node()->Emit(compiler, trace); + return; + } + + AssertGuardsMentionRegisters(trace); + + LimitResult limit_result = LimitVersions(compiler, trace); + if (limit_result == DONE) return; + DCHECK(limit_result == CONTINUE); + + // For loop nodes we already flushed (see LoopChoiceNode::Emit), but for + // other choice nodes we only flush if we are out of code size budget. + if (trace->flush_budget() == 0 && trace->actions() != nullptr) { + trace->Flush(compiler, this); + return; + } + + RecursionCheck rc(compiler); + + PreloadState preload; + preload.init(); + GreedyLoopState greedy_loop_state(not_at_start()); + + int text_length = GreedyLoopTextLengthForAlternative(&alternatives_->at(0)); + AlternativeGenerationList alt_gens(choice_count, zone()); + + if (choice_count > 1 && text_length != kNodeIsTooComplexForGreedyLoops) { + trace = EmitGreedyLoop(compiler, trace, &alt_gens, &preload, + &greedy_loop_state, text_length); + } else { + // TODO(erikcorry): Delete this. We don't need this label, but it makes us + // match the traces produced pre-cleanup. + Label second_choice; + compiler->macro_assembler()->Bind(&second_choice); + + preload.eats_at_least_ = EmitOptimizedUnanchoredSearch(compiler, trace); + + EmitChoices(compiler, &alt_gens, 0, trace, &preload); + } + + // At this point we need to generate slow checks for the alternatives where + // the quick check was inlined. We can recognize these because the associated + // label was bound. + int new_flush_budget = trace->flush_budget() / choice_count; + for (int i = 0; i < choice_count; i++) { + AlternativeGeneration* alt_gen = alt_gens.at(i); + Trace new_trace(*trace); + // If there are actions to be flushed we have to limit how many times + // they are flushed. Take the budget of the parent trace and distribute + // it fairly amongst the children. + if (new_trace.actions() != nullptr) { + new_trace.set_flush_budget(new_flush_budget); + } + bool next_expects_preload = + i == choice_count - 1 ? false : alt_gens.at(i + 1)->expects_preload; + EmitOutOfLineContinuation(compiler, &new_trace, alternatives_->at(i), + alt_gen, preload.preload_characters_, + next_expects_preload); + } +} + +Trace* ChoiceNode::EmitGreedyLoop(RegExpCompiler* compiler, Trace* trace, + AlternativeGenerationList* alt_gens, + PreloadState* preload, + GreedyLoopState* greedy_loop_state, + int text_length) { + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + // Here we have special handling for greedy loops containing only text nodes + // and other simple nodes. These are handled by pushing the current + // position on the stack and then incrementing the current position each + // time around the switch. On backtrack we decrement the current position + // and check it against the pushed value. This avoids pushing backtrack + // information for each iteration of the loop, which could take up a lot of + // space. + DCHECK(trace->stop_node() == nullptr); + macro_assembler->PushCurrentPosition(); + Label greedy_match_failed; + Trace greedy_match_trace; + if (not_at_start()) greedy_match_trace.set_at_start(Trace::FALSE_VALUE); + greedy_match_trace.set_backtrack(&greedy_match_failed); + Label loop_label; + macro_assembler->Bind(&loop_label); + greedy_match_trace.set_stop_node(this); + greedy_match_trace.set_loop_label(&loop_label); + alternatives_->at(0).node()->Emit(compiler, &greedy_match_trace); + macro_assembler->Bind(&greedy_match_failed); + + Label second_choice; // For use in greedy matches. + macro_assembler->Bind(&second_choice); + + Trace* new_trace = greedy_loop_state->counter_backtrack_trace(); + + EmitChoices(compiler, alt_gens, 1, new_trace, preload); + + macro_assembler->Bind(greedy_loop_state->label()); + // If we have unwound to the bottom then backtrack. + macro_assembler->CheckGreedyLoop(trace->backtrack()); + // Otherwise try the second priority at an earlier position. + macro_assembler->AdvanceCurrentPosition(-text_length); + macro_assembler->GoTo(&second_choice); + return new_trace; +} + +int ChoiceNode::EmitOptimizedUnanchoredSearch(RegExpCompiler* compiler, + Trace* trace) { + int eats_at_least = PreloadState::kEatsAtLeastNotYetInitialized; + if (alternatives_->length() != 2) return eats_at_least; + + GuardedAlternative alt1 = alternatives_->at(1); + if (alt1.guards() != nullptr && alt1.guards()->length() != 0) { + return eats_at_least; + } + RegExpNode* eats_anything_node = alt1.node(); + if (eats_anything_node->GetSuccessorOfOmnivorousTextNode(compiler) != this) { + return eats_at_least; + } + + // Really we should be creating a new trace when we execute this function, + // but there is no need, because the code it generates cannot backtrack, and + // we always arrive here with a trivial trace (since it's the entry to a + // loop. That also implies that there are no preloaded characters, which is + // good, because it means we won't be violating any assumptions by + // overwriting those characters with new load instructions. + DCHECK(trace->is_trivial()); + + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + Isolate* isolate = macro_assembler->isolate(); + // At this point we know that we are at a non-greedy loop that will eat + // any character one at a time. Any non-anchored regexp has such a + // loop prepended to it in order to find where it starts. We look for + // a pattern of the form ...abc... where we can look 6 characters ahead + // and step forwards 3 if the character is not one of abc. Abc need + // not be atoms, they can be any reasonably limited character class or + // small alternation. + BoyerMooreLookahead* bm = bm_info(false); + if (bm == nullptr) { + eats_at_least = std::min(kMaxLookaheadForBoyerMoore, EatsAtLeast(false)); + if (eats_at_least >= 1) { + bm = zone()->New<BoyerMooreLookahead>(eats_at_least, compiler, zone()); + GuardedAlternative alt0 = alternatives_->at(0); + alt0.node()->FillInBMInfo(isolate, 0, kRecursionBudget, bm, false); + } + } + if (bm != nullptr) { + bm->EmitSkipInstructions(macro_assembler); + } + return eats_at_least; +} + +void ChoiceNode::EmitChoices(RegExpCompiler* compiler, + AlternativeGenerationList* alt_gens, + int first_choice, Trace* trace, + PreloadState* preload) { + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + SetUpPreLoad(compiler, trace, preload); + + // For now we just call all choices one after the other. The idea ultimately + // is to use the Dispatch table to try only the relevant ones. + int choice_count = alternatives_->length(); + + int new_flush_budget = trace->flush_budget() / choice_count; + + for (int i = first_choice; i < choice_count; i++) { + bool is_last = i == choice_count - 1; + bool fall_through_on_failure = !is_last; + GuardedAlternative alternative = alternatives_->at(i); + AlternativeGeneration* alt_gen = alt_gens->at(i); + alt_gen->quick_check_details.set_characters(preload->preload_characters_); + ZoneList<Guard*>* guards = alternative.guards(); + int guard_count = (guards == nullptr) ? 0 : guards->length(); + Trace new_trace(*trace); + new_trace.set_characters_preloaded( + preload->preload_is_current_ ? preload->preload_characters_ : 0); + if (preload->preload_has_checked_bounds_) { + new_trace.set_bound_checked_up_to(preload->preload_characters_); + } + new_trace.quick_check_performed()->Clear(); + if (not_at_start_) new_trace.set_at_start(Trace::FALSE_VALUE); + if (!is_last) { + new_trace.set_backtrack(&alt_gen->after); + } + alt_gen->expects_preload = preload->preload_is_current_; + bool generate_full_check_inline = false; + if (compiler->optimize() && + try_to_emit_quick_check_for_alternative(i == 0) && + alternative.node()->EmitQuickCheck( + compiler, trace, &new_trace, preload->preload_has_checked_bounds_, + &alt_gen->possible_success, &alt_gen->quick_check_details, + fall_through_on_failure, this)) { + // Quick check was generated for this choice. + preload->preload_is_current_ = true; + preload->preload_has_checked_bounds_ = true; + // If we generated the quick check to fall through on possible success, + // we now need to generate the full check inline. + if (!fall_through_on_failure) { + macro_assembler->Bind(&alt_gen->possible_success); + new_trace.set_quick_check_performed(&alt_gen->quick_check_details); + new_trace.set_characters_preloaded(preload->preload_characters_); + new_trace.set_bound_checked_up_to(preload->preload_characters_); + generate_full_check_inline = true; + } + } else if (alt_gen->quick_check_details.cannot_match()) { + if (!fall_through_on_failure) { + macro_assembler->GoTo(trace->backtrack()); + } + continue; + } else { + // No quick check was generated. Put the full code here. + // If this is not the first choice then there could be slow checks from + // previous cases that go here when they fail. There's no reason to + // insist that they preload characters since the slow check we are about + // to generate probably can't use it. + if (i != first_choice) { + alt_gen->expects_preload = false; + new_trace.InvalidateCurrentCharacter(); + } + generate_full_check_inline = true; + } + if (generate_full_check_inline) { + if (new_trace.actions() != nullptr) { + new_trace.set_flush_budget(new_flush_budget); + } + for (int j = 0; j < guard_count; j++) { + GenerateGuard(macro_assembler, guards->at(j), &new_trace); + } + alternative.node()->Emit(compiler, &new_trace); + preload->preload_is_current_ = false; + } + macro_assembler->Bind(&alt_gen->after); + } +} + +void ChoiceNode::EmitOutOfLineContinuation(RegExpCompiler* compiler, + Trace* trace, + GuardedAlternative alternative, + AlternativeGeneration* alt_gen, + int preload_characters, + bool next_expects_preload) { + if (!alt_gen->possible_success.is_linked()) return; + + RegExpMacroAssembler* macro_assembler = compiler->macro_assembler(); + macro_assembler->Bind(&alt_gen->possible_success); + Trace out_of_line_trace(*trace); + out_of_line_trace.set_characters_preloaded(preload_characters); + out_of_line_trace.set_quick_check_performed(&alt_gen->quick_check_details); + if (not_at_start_) out_of_line_trace.set_at_start(Trace::FALSE_VALUE); + ZoneList<Guard*>* guards = alternative.guards(); + int guard_count = (guards == nullptr) ? 0 : guards->length(); + if (next_expects_preload) { + Label reload_current_char; + out_of_line_trace.set_backtrack(&reload_current_char); + for (int j = 0; j < guard_count; j++) { + GenerateGuard(macro_assembler, guards->at(j), &out_of_line_trace); + } + alternative.node()->Emit(compiler, &out_of_line_trace); + macro_assembler->Bind(&reload_current_char); + // Reload the current character, since the next quick check expects that. + // We don't need to check bounds here because we only get into this + // code through a quick check which already did the checked load. + macro_assembler->LoadCurrentCharacter(trace->cp_offset(), nullptr, false, + preload_characters); + macro_assembler->GoTo(&(alt_gen->after)); + } else { + out_of_line_trace.set_backtrack(&(alt_gen->after)); + for (int j = 0; j < guard_count; j++) { + GenerateGuard(macro_assembler, guards->at(j), &out_of_line_trace); + } + alternative.node()->Emit(compiler, &out_of_line_trace); + } +} + +void ActionNode::Emit(RegExpCompiler* compiler, Trace* trace) { + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + LimitResult limit_result = LimitVersions(compiler, trace); + if (limit_result == DONE) return; + DCHECK(limit_result == CONTINUE); + + RecursionCheck rc(compiler); + + switch (action_type_) { + case STORE_POSITION: { + Trace::DeferredCapture new_capture(data_.u_position_register.reg, + data_.u_position_register.is_capture, + trace); + Trace new_trace = *trace; + new_trace.add_action(&new_capture); + on_success()->Emit(compiler, &new_trace); + break; + } + case INCREMENT_REGISTER: { + Trace::DeferredIncrementRegister new_increment( + data_.u_increment_register.reg); + Trace new_trace = *trace; + new_trace.add_action(&new_increment); + on_success()->Emit(compiler, &new_trace); + break; + } + case SET_REGISTER_FOR_LOOP: { + Trace::DeferredSetRegisterForLoop new_set(data_.u_store_register.reg, + data_.u_store_register.value); + Trace new_trace = *trace; + new_trace.add_action(&new_set); + on_success()->Emit(compiler, &new_trace); + break; + } + case CLEAR_CAPTURES: { + Trace::DeferredClearCaptures new_capture(Interval( + data_.u_clear_captures.range_from, data_.u_clear_captures.range_to)); + Trace new_trace = *trace; + new_trace.add_action(&new_capture); + on_success()->Emit(compiler, &new_trace); + break; + } + case BEGIN_POSITIVE_SUBMATCH: + case BEGIN_NEGATIVE_SUBMATCH: + if (!trace->is_trivial()) { + trace->Flush(compiler, this); + } else { + assembler->WriteCurrentPositionToRegister( + data_.u_submatch.current_position_register, 0); + assembler->WriteStackPointerToRegister( + data_.u_submatch.stack_pointer_register); + on_success()->Emit(compiler, trace); + } + break; + case EMPTY_MATCH_CHECK: { + int start_pos_reg = data_.u_empty_match_check.start_register; + int stored_pos = 0; + int rep_reg = data_.u_empty_match_check.repetition_register; + bool has_minimum = (rep_reg != RegExpCompiler::kNoRegister); + bool know_dist = trace->GetStoredPosition(start_pos_reg, &stored_pos); + if (know_dist && !has_minimum && stored_pos == trace->cp_offset()) { + // If we know we haven't advanced and there is no minimum we + // can just backtrack immediately. + assembler->GoTo(trace->backtrack()); + } else if (know_dist && stored_pos < trace->cp_offset()) { + // If we know we've advanced we can generate the continuation + // immediately. + on_success()->Emit(compiler, trace); + } else if (!trace->is_trivial()) { + trace->Flush(compiler, this); + } else { + Label skip_empty_check; + // If we have a minimum number of repetitions we check the current + // number first and skip the empty check if it's not enough. + if (has_minimum) { + int limit = data_.u_empty_match_check.repetition_limit; + assembler->IfRegisterLT(rep_reg, limit, &skip_empty_check); + } + // If the match is empty we bail out, otherwise we fall through + // to the on-success continuation. + assembler->IfRegisterEqPos(data_.u_empty_match_check.start_register, + trace->backtrack()); + assembler->Bind(&skip_empty_check); + on_success()->Emit(compiler, trace); + } + break; + } + case POSITIVE_SUBMATCH_SUCCESS: { + if (!trace->is_trivial()) { + trace->Flush(compiler, this); + return; + } + assembler->ReadCurrentPositionFromRegister( + data_.u_submatch.current_position_register); + assembler->ReadStackPointerFromRegister( + data_.u_submatch.stack_pointer_register); + int clear_register_count = data_.u_submatch.clear_register_count; + if (clear_register_count == 0) { + on_success()->Emit(compiler, trace); + return; + } + int clear_registers_from = data_.u_submatch.clear_register_from; + Label clear_registers_backtrack; + Trace new_trace = *trace; + new_trace.set_backtrack(&clear_registers_backtrack); + on_success()->Emit(compiler, &new_trace); + + assembler->Bind(&clear_registers_backtrack); + int clear_registers_to = clear_registers_from + clear_register_count - 1; + assembler->ClearRegisters(clear_registers_from, clear_registers_to); + + DCHECK(trace->backtrack() == nullptr); + assembler->Backtrack(); + return; + } + default: + UNREACHABLE(); + } +} + +void BackReferenceNode::Emit(RegExpCompiler* compiler, Trace* trace) { + RegExpMacroAssembler* assembler = compiler->macro_assembler(); + if (!trace->is_trivial()) { + trace->Flush(compiler, this); + return; + } + + LimitResult limit_result = LimitVersions(compiler, trace); + if (limit_result == DONE) return; + DCHECK(limit_result == CONTINUE); + + RecursionCheck rc(compiler); + + DCHECK_EQ(start_reg_ + 1, end_reg_); + if (IsIgnoreCase(flags_)) { + bool unicode = IsEitherUnicode(flags_); + assembler->CheckNotBackReferenceIgnoreCase(start_reg_, read_backward(), + unicode, trace->backtrack()); + } else { + assembler->CheckNotBackReference(start_reg_, read_backward(), + trace->backtrack()); + } + // We are going to advance backward, so we may end up at the start. + if (read_backward()) trace->set_at_start(Trace::UNKNOWN); + + // Check that the back reference does not end inside a surrogate pair. + if (IsEitherUnicode(flags_) && !compiler->one_byte()) { + assembler->CheckNotInSurrogatePair(trace->cp_offset(), trace->backtrack()); + } + on_success()->Emit(compiler, trace); +} + +void TextNode::CalculateOffsets() { + int element_count = elements()->length(); + // Set up the offsets of the elements relative to the start. This is a fixed + // quantity since a TextNode can only contain fixed-width things. + int cp_offset = 0; + for (int i = 0; i < element_count; i++) { + TextElement& elm = elements()->at(i); + elm.set_cp_offset(cp_offset); + cp_offset += elm.length(); + } +} + +namespace { + +// Assertion propagation moves information about assertions such as +// \b to the affected nodes. For instance, in /.\b./ information must +// be propagated to the first '.' that whatever follows needs to know +// if it matched a word or a non-word, and to the second '.' that it +// has to check if it succeeds a word or non-word. In this case the +// result will be something like: +// +// +-------+ +------------+ +// | . | | . | +// +-------+ ---> +------------+ +// | word? | | check word | +// +-------+ +------------+ +class AssertionPropagator : public AllStatic { + public: + static void VisitText(TextNode* that) {} + + static void VisitAction(ActionNode* that) { + // If the next node is interested in what it follows then this node + // has to be interested too so it can pass the information on. + that->info()->AddFromFollowing(that->on_success()->info()); + } + + static void VisitChoice(ChoiceNode* that, int i) { + // Anything the following nodes need to know has to be known by + // this node also, so it can pass it on. + that->info()->AddFromFollowing(that->alternatives()->at(i).node()->info()); + } + + static void VisitLoopChoiceContinueNode(LoopChoiceNode* that) { + that->info()->AddFromFollowing(that->continue_node()->info()); + } + + static void VisitLoopChoiceLoopNode(LoopChoiceNode* that) { + that->info()->AddFromFollowing(that->loop_node()->info()); + } + + static void VisitNegativeLookaroundChoiceLookaroundNode( + NegativeLookaroundChoiceNode* that) { + VisitChoice(that, NegativeLookaroundChoiceNode::kLookaroundIndex); + } + + static void VisitNegativeLookaroundChoiceContinueNode( + NegativeLookaroundChoiceNode* that) { + VisitChoice(that, NegativeLookaroundChoiceNode::kContinueIndex); + } + + static void VisitBackReference(BackReferenceNode* that) {} + + static void VisitAssertion(AssertionNode* that) {} +}; + +// Propagates information about the minimum size of successful matches from +// successor nodes to their predecessors. Note that all eats_at_least values +// are initialized to zero before analysis. +class EatsAtLeastPropagator : public AllStatic { + public: + static void VisitText(TextNode* that) { + // The eats_at_least value is not used if reading backward. + if (!that->read_backward()) { + // We are not at the start after this node, and thus we can use the + // successor's eats_at_least_from_not_start value. + uint8_t eats_at_least = base::saturated_cast<uint8_t>( + that->Length() + that->on_success() + ->eats_at_least_info() + ->eats_at_least_from_not_start); + that->set_eats_at_least_info(EatsAtLeastInfo(eats_at_least)); + } + } + + static void VisitAction(ActionNode* that) { + switch (that->action_type()) { + case ActionNode::BEGIN_POSITIVE_SUBMATCH: + case ActionNode::POSITIVE_SUBMATCH_SUCCESS: + // We do not propagate eats_at_least data through positive lookarounds, + // because they rewind input. + // TODO(v8:11859) Potential approaches for fixing this include: + // 1. Add a dedicated choice node for positive lookaround, similar to + // NegativeLookaroundChoiceNode. + // 2. Add an eats_at_least_inside_loop field to EatsAtLeastInfo, which + // is <= eats_at_least_from_possibly_start, and use that value in + // EatsAtLeastFromLoopEntry. + DCHECK(that->eats_at_least_info()->IsZero()); + break; + case ActionNode::SET_REGISTER_FOR_LOOP: + // SET_REGISTER_FOR_LOOP indicates a loop entry point, which means the + // loop body will run at least the minimum number of times before the + // continuation case can run. + that->set_eats_at_least_info( + that->on_success()->EatsAtLeastFromLoopEntry()); + break; + case ActionNode::BEGIN_NEGATIVE_SUBMATCH: + default: + // Otherwise, the current node eats at least as much as its successor. + // Note: we can propagate eats_at_least data for BEGIN_NEGATIVE_SUBMATCH + // because NegativeLookaroundChoiceNode ignores its lookaround successor + // when computing eats-at-least and quick check information. + that->set_eats_at_least_info(*that->on_success()->eats_at_least_info()); + break; + } + } + + static void VisitChoice(ChoiceNode* that, int i) { + // The minimum possible match from a choice node is the minimum of its + // successors. + EatsAtLeastInfo eats_at_least = + i == 0 ? EatsAtLeastInfo(UINT8_MAX) : *that->eats_at_least_info(); + eats_at_least.SetMin( + *that->alternatives()->at(i).node()->eats_at_least_info()); + that->set_eats_at_least_info(eats_at_least); + } + + static void VisitLoopChoiceContinueNode(LoopChoiceNode* that) { + if (!that->read_backward()) { + that->set_eats_at_least_info( + *that->continue_node()->eats_at_least_info()); + } + } + + static void VisitLoopChoiceLoopNode(LoopChoiceNode* that) {} + + static void VisitNegativeLookaroundChoiceLookaroundNode( + NegativeLookaroundChoiceNode* that) {} + + static void VisitNegativeLookaroundChoiceContinueNode( + NegativeLookaroundChoiceNode* that) { + that->set_eats_at_least_info(*that->continue_node()->eats_at_least_info()); + } + + static void VisitBackReference(BackReferenceNode* that) { + if (!that->read_backward()) { + that->set_eats_at_least_info(*that->on_success()->eats_at_least_info()); + } + } + + static void VisitAssertion(AssertionNode* that) { + EatsAtLeastInfo eats_at_least = *that->on_success()->eats_at_least_info(); + if (that->assertion_type() == AssertionNode::AT_START) { + // If we know we are not at the start and we are asked "how many + // characters will you match if you succeed?" then we can answer anything + // since false implies false. So let's just set the max answer + // (UINT8_MAX) since that won't prevent us from preloading a lot of + // characters for the other branches in the node graph. + eats_at_least.eats_at_least_from_not_start = UINT8_MAX; + } + that->set_eats_at_least_info(eats_at_least); + } +}; + +} // namespace + +// ------------------------------------------------------------------- +// Analysis + +// Iterates the node graph and provides the opportunity for propagators to set +// values that depend on successor nodes. +template <typename... Propagators> +class Analysis : public NodeVisitor { + public: + Analysis(Isolate* isolate, bool is_one_byte, RegExpFlags flags) + : isolate_(isolate), + is_one_byte_(is_one_byte), + flags_(flags), + error_(RegExpError::kNone) {} + + void EnsureAnalyzed(RegExpNode* that) { + StackLimitCheck check(isolate()); + if (check.HasOverflowed()) { + if (v8_flags.correctness_fuzzer_suppressions) { + FATAL("Analysis: Aborting on stack overflow"); + } + fail(RegExpError::kAnalysisStackOverflow); + return; + } + if (that->info()->been_analyzed || that->info()->being_analyzed) return; + that->info()->being_analyzed = true; + that->Accept(this); + that->info()->being_analyzed = false; + that->info()->been_analyzed = true; + } + + bool has_failed() { return error_ != RegExpError::kNone; } + RegExpError error() { + DCHECK(error_ != RegExpError::kNone); + return error_; + } + void fail(RegExpError error) { error_ = error; } + + Isolate* isolate() const { return isolate_; } + + void VisitEnd(EndNode* that) override { + // nothing to do + } + +// Used to call the given static function on each propagator / variadic template +// argument. +#define STATIC_FOR_EACH(expr) \ + do { \ + int dummy[] = {((expr), 0)...}; \ + USE(dummy); \ + } while (false) + + void VisitText(TextNode* that) override { + that->MakeCaseIndependent(isolate(), is_one_byte_, flags_); + EnsureAnalyzed(that->on_success()); + if (has_failed()) return; + that->CalculateOffsets(); + STATIC_FOR_EACH(Propagators::VisitText(that)); + } + + void VisitAction(ActionNode* that) override { + EnsureAnalyzed(that->on_success()); + if (has_failed()) return; + STATIC_FOR_EACH(Propagators::VisitAction(that)); + } + + void VisitChoice(ChoiceNode* that) override { + for (int i = 0; i < that->alternatives()->length(); i++) { + EnsureAnalyzed(that->alternatives()->at(i).node()); + if (has_failed()) return; + STATIC_FOR_EACH(Propagators::VisitChoice(that, i)); + } + } + + void VisitLoopChoice(LoopChoiceNode* that) override { + DCHECK_EQ(that->alternatives()->length(), 2); // Just loop and continue. + + // First propagate all information from the continuation node. + EnsureAnalyzed(that->continue_node()); + if (has_failed()) return; + STATIC_FOR_EACH(Propagators::VisitLoopChoiceContinueNode(that)); + + // Check the loop last since it may need the value of this node + // to get a correct result. + EnsureAnalyzed(that->loop_node()); + if (has_failed()) return; + STATIC_FOR_EACH(Propagators::VisitLoopChoiceLoopNode(that)); + } + + void VisitNegativeLookaroundChoice( + NegativeLookaroundChoiceNode* that) override { + DCHECK_EQ(that->alternatives()->length(), 2); // Lookaround and continue. + + EnsureAnalyzed(that->lookaround_node()); + if (has_failed()) return; + STATIC_FOR_EACH( + Propagators::VisitNegativeLookaroundChoiceLookaroundNode(that)); + + EnsureAnalyzed(that->continue_node()); + if (has_failed()) return; + STATIC_FOR_EACH( + Propagators::VisitNegativeLookaroundChoiceContinueNode(that)); + } + + void VisitBackReference(BackReferenceNode* that) override { + EnsureAnalyzed(that->on_success()); + if (has_failed()) return; + STATIC_FOR_EACH(Propagators::VisitBackReference(that)); + } + + void VisitAssertion(AssertionNode* that) override { + EnsureAnalyzed(that->on_success()); + if (has_failed()) return; + STATIC_FOR_EACH(Propagators::VisitAssertion(that)); + } + +#undef STATIC_FOR_EACH + + private: + Isolate* isolate_; + const bool is_one_byte_; + const RegExpFlags flags_; + RegExpError error_; + + DISALLOW_IMPLICIT_CONSTRUCTORS(Analysis); +}; + +RegExpError AnalyzeRegExp(Isolate* isolate, bool is_one_byte, RegExpFlags flags, + RegExpNode* node) { + Analysis<AssertionPropagator, EatsAtLeastPropagator> analysis( + isolate, is_one_byte, flags); + DCHECK_EQ(node->info()->been_analyzed, false); + analysis.EnsureAnalyzed(node); + DCHECK_IMPLIES(analysis.has_failed(), analysis.error() != RegExpError::kNone); + return analysis.has_failed() ? analysis.error() : RegExpError::kNone; +} + +void BackReferenceNode::FillInBMInfo(Isolate* isolate, int offset, int budget, + BoyerMooreLookahead* bm, + bool not_at_start) { + // Working out the set of characters that a backreference can match is too + // hard, so we just say that any character can match. + bm->SetRest(offset); + SaveBMInfo(bm, not_at_start, offset); +} + +static_assert(BoyerMoorePositionInfo::kMapSize == + RegExpMacroAssembler::kTableSize); + +void ChoiceNode::FillInBMInfo(Isolate* isolate, int offset, int budget, + BoyerMooreLookahead* bm, bool not_at_start) { + ZoneList<GuardedAlternative>* alts = alternatives(); + budget = (budget - 1) / alts->length(); + for (int i = 0; i < alts->length(); i++) { + GuardedAlternative& alt = alts->at(i); + if (alt.guards() != nullptr && alt.guards()->length() != 0) { + bm->SetRest(offset); // Give up trying to fill in info. + SaveBMInfo(bm, not_at_start, offset); + return; + } + alt.node()->FillInBMInfo(isolate, offset, budget, bm, not_at_start); + } + SaveBMInfo(bm, not_at_start, offset); +} + +void TextNode::FillInBMInfo(Isolate* isolate, int initial_offset, int budget, + BoyerMooreLookahead* bm, bool not_at_start) { + if (initial_offset >= bm->length()) return; + int offset = initial_offset; + int max_char = bm->max_char(); + for (int i = 0; i < elements()->length(); i++) { + if (offset >= bm->length()) { + if (initial_offset == 0) set_bm_info(not_at_start, bm); + return; + } + TextElement text = elements()->at(i); + if (text.text_type() == TextElement::ATOM) { + RegExpAtom* atom = text.atom(); + for (int j = 0; j < atom->length(); j++, offset++) { + if (offset >= bm->length()) { + if (initial_offset == 0) set_bm_info(not_at_start, bm); + return; + } + base::uc16 character = atom->data()[j]; + if (IsIgnoreCase(bm->compiler()->flags())) { + unibrow::uchar chars[4]; + int length = GetCaseIndependentLetters( + isolate, character, bm->max_char() == String::kMaxOneByteCharCode, + chars, 4); + for (int k = 0; k < length; k++) { + bm->Set(offset, chars[k]); + } + } else { + if (character <= max_char) bm->Set(offset, character); + } + } + } else { + DCHECK_EQ(TextElement::CLASS_RANGES, text.text_type()); + RegExpClassRanges* class_ranges = text.class_ranges(); + ZoneList<CharacterRange>* ranges = class_ranges->ranges(zone()); + if (class_ranges->is_negated()) { + bm->SetAll(offset); + } else { + for (int k = 0; k < ranges->length(); k++) { + CharacterRange& range = ranges->at(k); + if (static_cast<int>(range.from()) > max_char) continue; + int to = std::min(max_char, static_cast<int>(range.to())); + bm->SetInterval(offset, Interval(range.from(), to)); + } + } + offset++; + } + } + if (offset >= bm->length()) { + if (initial_offset == 0) set_bm_info(not_at_start, bm); + return; + } + on_success()->FillInBMInfo(isolate, offset, budget - 1, bm, + true); // Not at start after a text node. + if (initial_offset == 0) set_bm_info(not_at_start, bm); +} + +RegExpNode* RegExpCompiler::OptionallyStepBackToLeadSurrogate( + RegExpNode* on_success) { + DCHECK(!read_backward()); + ZoneList<CharacterRange>* lead_surrogates = CharacterRange::List( + zone(), CharacterRange::Range(kLeadSurrogateStart, kLeadSurrogateEnd)); + ZoneList<CharacterRange>* trail_surrogates = CharacterRange::List( + zone(), CharacterRange::Range(kTrailSurrogateStart, kTrailSurrogateEnd)); + + ChoiceNode* optional_step_back = zone()->New<ChoiceNode>(2, zone()); + + int stack_register = UnicodeLookaroundStackRegister(); + int position_register = UnicodeLookaroundPositionRegister(); + RegExpNode* step_back = TextNode::CreateForCharacterRanges( + zone(), lead_surrogates, true, on_success); + RegExpLookaround::Builder builder(true, step_back, stack_register, + position_register); + RegExpNode* match_trail = TextNode::CreateForCharacterRanges( + zone(), trail_surrogates, false, builder.on_match_success()); + + optional_step_back->AddAlternative( + GuardedAlternative(builder.ForMatch(match_trail))); + optional_step_back->AddAlternative(GuardedAlternative(on_success)); + + return optional_step_back; +} + +RegExpNode* RegExpCompiler::PreprocessRegExp(RegExpCompileData* data, + RegExpFlags flags, + bool is_one_byte) { + // Wrap the body of the regexp in capture #0. + RegExpNode* captured_body = + RegExpCapture::ToNode(data->tree, 0, this, accept()); + RegExpNode* node = captured_body; + if (!data->tree->IsAnchoredAtStart() && !IsSticky(flags)) { + // Add a .*? at the beginning, outside the body capture, unless + // this expression is anchored at the beginning or sticky. + RegExpNode* loop_node = RegExpQuantifier::ToNode( + 0, RegExpTree::kInfinity, false, + zone()->New<RegExpClassRanges>(StandardCharacterSet::kEverything), this, + captured_body, data->contains_anchor); + + if (data->contains_anchor) { + // Unroll loop once, to take care of the case that might start + // at the start of input. + ChoiceNode* first_step_node = zone()->New<ChoiceNode>(2, zone()); + first_step_node->AddAlternative(GuardedAlternative(captured_body)); + first_step_node->AddAlternative(GuardedAlternative(zone()->New<TextNode>( + zone()->New<RegExpClassRanges>(StandardCharacterSet::kEverything), + false, loop_node))); + node = first_step_node; + } else { + node = loop_node; + } + } + if (is_one_byte) { + node = node->FilterOneByte(RegExpCompiler::kMaxRecursion, flags); + // Do it again to propagate the new nodes to places where they were not + // put because they had not been calculated yet. + if (node != nullptr) { + node = node->FilterOneByte(RegExpCompiler::kMaxRecursion, flags); + } + } else if (IsEitherUnicode(flags) && (IsGlobal(flags) || IsSticky(flags))) { + node = OptionallyStepBackToLeadSurrogate(node); + } + + if (node == nullptr) node = zone()->New<EndNode>(EndNode::BACKTRACK, zone()); + return node; +} + +void RegExpCompiler::ToNodeCheckForStackOverflow() { + if (StackLimitCheck{isolate()}.HasOverflowed()) { + V8::FatalProcessOutOfMemory(isolate(), "RegExpCompiler"); + } +} + +} // namespace internal +} // namespace v8 diff --git a/js/src/irregexp/imported/regexp-compiler.h b/js/src/irregexp/imported/regexp-compiler.h new file mode 100644 index 0000000000..91dd43ab8a --- /dev/null +++ b/js/src/irregexp/imported/regexp-compiler.h @@ -0,0 +1,621 @@ +// 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. + +#ifndef V8_REGEXP_REGEXP_COMPILER_H_ +#define V8_REGEXP_REGEXP_COMPILER_H_ + +#include <bitset> + +#include "irregexp/imported/regexp-nodes.h" + +namespace v8 { +namespace internal { + +class DynamicBitSet; +class Isolate; + +namespace regexp_compiler_constants { + +// The '2' variant is has inclusive from and exclusive to. +// This covers \s as defined in ECMA-262 5.1, 15.10.2.12, +// which include WhiteSpace (7.2) or LineTerminator (7.3) values. +constexpr base::uc32 kRangeEndMarker = 0x110000; +constexpr int kSpaceRanges[] = { + '\t', '\r' + 1, ' ', ' ' + 1, 0x00A0, 0x00A1, 0x1680, + 0x1681, 0x2000, 0x200B, 0x2028, 0x202A, 0x202F, 0x2030, + 0x205F, 0x2060, 0x3000, 0x3001, 0xFEFF, 0xFF00, kRangeEndMarker}; +constexpr int kSpaceRangeCount = arraysize(kSpaceRanges); + +constexpr int kWordRanges[] = {'0', '9' + 1, 'A', 'Z' + 1, '_', + '_' + 1, 'a', 'z' + 1, kRangeEndMarker}; +constexpr int kWordRangeCount = arraysize(kWordRanges); +constexpr int kDigitRanges[] = {'0', '9' + 1, kRangeEndMarker}; +constexpr int kDigitRangeCount = arraysize(kDigitRanges); +constexpr int kSurrogateRanges[] = {kLeadSurrogateStart, + kLeadSurrogateStart + 1, kRangeEndMarker}; +constexpr int kSurrogateRangeCount = arraysize(kSurrogateRanges); +constexpr int kLineTerminatorRanges[] = {0x000A, 0x000B, 0x000D, 0x000E, + 0x2028, 0x202A, kRangeEndMarker}; +constexpr int kLineTerminatorRangeCount = arraysize(kLineTerminatorRanges); + +// More makes code generation slower, less makes V8 benchmark score lower. +constexpr int kMaxLookaheadForBoyerMoore = 8; +// In a 3-character pattern you can maximally step forwards 3 characters +// at a time, which is not always enough to pay for the extra logic. +constexpr int kPatternTooShortForBoyerMoore = 2; + +} // namespace regexp_compiler_constants + +inline bool NeedsUnicodeCaseEquivalents(RegExpFlags flags) { + // Both unicode (or unicode sets) and ignore_case flags are set. We need to + // use ICU to find the closure over case equivalents. + return IsEitherUnicode(flags) && IsIgnoreCase(flags); +} + +// Details of a quick mask-compare check that can look ahead in the +// input stream. +class QuickCheckDetails { + public: + QuickCheckDetails() + : characters_(0), mask_(0), value_(0), cannot_match_(false) {} + explicit QuickCheckDetails(int characters) + : characters_(characters), mask_(0), value_(0), cannot_match_(false) {} + bool Rationalize(bool one_byte); + // Merge in the information from another branch of an alternation. + void Merge(QuickCheckDetails* other, int from_index); + // Advance the current position by some amount. + void Advance(int by, bool one_byte); + void Clear(); + bool cannot_match() { return cannot_match_; } + void set_cannot_match() { cannot_match_ = true; } + struct Position { + Position() : mask(0), value(0), determines_perfectly(false) {} + base::uc32 mask; + base::uc32 value; + bool determines_perfectly; + }; + int characters() { return characters_; } + void set_characters(int characters) { characters_ = characters; } + Position* positions(int index) { + DCHECK_LE(0, index); + DCHECK_GT(characters_, index); + return positions_ + index; + } + uint32_t mask() { return mask_; } + uint32_t value() { return value_; } + + private: + // How many characters do we have quick check information from. This is + // the same for all branches of a choice node. + int characters_; + Position positions_[4]; + // These values are the condensate of the above array after Rationalize(). + uint32_t mask_; + uint32_t value_; + // If set to true, there is no way this quick check can match at all. + // E.g., if it requires to be at the start of the input, and isn't. + bool cannot_match_; +}; + +// Improve the speed that we scan for an initial point where a non-anchored +// regexp can match by using a Boyer-Moore-like table. This is done by +// identifying non-greedy non-capturing loops in the nodes that eat any +// character one at a time. For example in the middle of the regexp +// /foo[\s\S]*?bar/ we find such a loop. There is also such a loop implicitly +// inserted at the start of any non-anchored regexp. +// +// When we have found such a loop we look ahead in the nodes to find the set of +// characters that can come at given distances. For example for the regexp +// /.?foo/ we know that there are at least 3 characters ahead of us, and the +// sets of characters that can occur are [any, [f, o], [o]]. We find a range in +// the lookahead info where the set of characters is reasonably constrained. In +// our example this is from index 1 to 2 (0 is not constrained). We can now +// look 3 characters ahead and if we don't find one of [f, o] (the union of +// [f, o] and [o]) then we can skip forwards by the range size (in this case 2). +// +// For Unicode input strings we do the same, but modulo 128. +// +// We also look at the first string fed to the regexp and use that to get a hint +// of the character frequencies in the inputs. This affects the assessment of +// whether the set of characters is 'reasonably constrained'. +// +// We also have another lookahead mechanism (called quick check in the code), +// which uses a wide load of multiple characters followed by a mask and compare +// to determine whether a match is possible at this point. +enum ContainedInLattice { + kNotYet = 0, + kLatticeIn = 1, + kLatticeOut = 2, + kLatticeUnknown = 3 // Can also mean both in and out. +}; + +inline ContainedInLattice Combine(ContainedInLattice a, ContainedInLattice b) { + return static_cast<ContainedInLattice>(a | b); +} + +class BoyerMoorePositionInfo : public ZoneObject { + public: + bool at(int i) const { return map_[i]; } + + static constexpr int kMapSize = 128; + static constexpr int kMask = kMapSize - 1; + + int map_count() const { return map_count_; } + + void Set(int character); + void SetInterval(const Interval& interval); + void SetAll(); + + bool is_non_word() { return w_ == kLatticeOut; } + bool is_word() { return w_ == kLatticeIn; } + + using Bitset = std::bitset<kMapSize>; + Bitset raw_bitset() const { return map_; } + + private: + Bitset map_; + int map_count_ = 0; // Number of set bits in the map. + ContainedInLattice w_ = kNotYet; // The \w character class. +}; + +class BoyerMooreLookahead : public ZoneObject { + public: + BoyerMooreLookahead(int length, RegExpCompiler* compiler, Zone* zone); + + int length() { return length_; } + int max_char() { return max_char_; } + RegExpCompiler* compiler() { return compiler_; } + + int Count(int map_number) { return bitmaps_->at(map_number)->map_count(); } + + BoyerMoorePositionInfo* at(int i) { return bitmaps_->at(i); } + + void Set(int map_number, int character) { + if (character > max_char_) return; + BoyerMoorePositionInfo* info = bitmaps_->at(map_number); + info->Set(character); + } + + void SetInterval(int map_number, const Interval& interval) { + if (interval.from() > max_char_) return; + BoyerMoorePositionInfo* info = bitmaps_->at(map_number); + if (interval.to() > max_char_) { + info->SetInterval(Interval(interval.from(), max_char_)); + } else { + info->SetInterval(interval); + } + } + + void SetAll(int map_number) { bitmaps_->at(map_number)->SetAll(); } + + void SetRest(int from_map) { + for (int i = from_map; i < length_; i++) SetAll(i); + } + void EmitSkipInstructions(RegExpMacroAssembler* masm); + + private: + // This is the value obtained by EatsAtLeast. If we do not have at least this + // many characters left in the sample string then the match is bound to fail. + // Therefore it is OK to read a character this far ahead of the current match + // point. + int length_; + RegExpCompiler* compiler_; + // 0xff for Latin1, 0xffff for UTF-16. + int max_char_; + ZoneList<BoyerMoorePositionInfo*>* bitmaps_; + + int GetSkipTable(int min_lookahead, int max_lookahead, + Handle<ByteArray> boolean_skip_table); + bool FindWorthwhileInterval(int* from, int* to); + int FindBestInterval(int max_number_of_chars, int old_biggest_points, + int* from, int* to); +}; + +// There are many ways to generate code for a node. This class encapsulates +// the current way we should be generating. In other words it encapsulates +// the current state of the code generator. The effect of this is that we +// generate code for paths that the matcher can take through the regular +// expression. A given node in the regexp can be code-generated several times +// as it can be part of several traces. For example for the regexp: +// /foo(bar|ip)baz/ the code to match baz will be generated twice, once as part +// of the foo-bar-baz trace and once as part of the foo-ip-baz trace. The code +// to match foo is generated only once (the traces have a common prefix). The +// code to store the capture is deferred and generated (twice) after the places +// where baz has been matched. +class Trace { + public: + // A value for a property that is either known to be true, know to be false, + // or not known. + enum TriBool { UNKNOWN = -1, FALSE_VALUE = 0, TRUE_VALUE = 1 }; + + class DeferredAction { + public: + DeferredAction(ActionNode::ActionType action_type, int reg) + : action_type_(action_type), reg_(reg), next_(nullptr) {} + DeferredAction* next() { return next_; } + bool Mentions(int reg); + int reg() { return reg_; } + ActionNode::ActionType action_type() { return action_type_; } + + private: + ActionNode::ActionType action_type_; + int reg_; + DeferredAction* next_; + friend class Trace; + }; + + class DeferredCapture : public DeferredAction { + public: + DeferredCapture(int reg, bool is_capture, Trace* trace) + : DeferredAction(ActionNode::STORE_POSITION, reg), + cp_offset_(trace->cp_offset()), + is_capture_(is_capture) {} + int cp_offset() { return cp_offset_; } + bool is_capture() { return is_capture_; } + + private: + int cp_offset_; + bool is_capture_; + void set_cp_offset(int cp_offset) { cp_offset_ = cp_offset; } + }; + + class DeferredSetRegisterForLoop : public DeferredAction { + public: + DeferredSetRegisterForLoop(int reg, int value) + : DeferredAction(ActionNode::SET_REGISTER_FOR_LOOP, reg), + value_(value) {} + int value() { return value_; } + + private: + int value_; + }; + + class DeferredClearCaptures : public DeferredAction { + public: + explicit DeferredClearCaptures(Interval range) + : DeferredAction(ActionNode::CLEAR_CAPTURES, -1), range_(range) {} + Interval range() { return range_; } + + private: + Interval range_; + }; + + class DeferredIncrementRegister : public DeferredAction { + public: + explicit DeferredIncrementRegister(int reg) + : DeferredAction(ActionNode::INCREMENT_REGISTER, reg) {} + }; + + Trace() + : cp_offset_(0), + actions_(nullptr), + backtrack_(nullptr), + stop_node_(nullptr), + loop_label_(nullptr), + characters_preloaded_(0), + bound_checked_up_to_(0), + flush_budget_(100), + at_start_(UNKNOWN) {} + + // End the trace. This involves flushing the deferred actions in the trace + // and pushing a backtrack location onto the backtrack stack. Once this is + // done we can start a new trace or go to one that has already been + // generated. + void Flush(RegExpCompiler* compiler, RegExpNode* successor); + int cp_offset() { return cp_offset_; } + DeferredAction* actions() { return actions_; } + // A trivial trace is one that has no deferred actions or other state that + // affects the assumptions used when generating code. There is no recorded + // backtrack location in a trivial trace, so with a trivial trace we will + // generate code that, on a failure to match, gets the backtrack location + // from the backtrack stack rather than using a direct jump instruction. We + // always start code generation with a trivial trace and non-trivial traces + // are created as we emit code for nodes or add to the list of deferred + // actions in the trace. The location of the code generated for a node using + // a trivial trace is recorded in a label in the node so that gotos can be + // generated to that code. + bool is_trivial() { + return backtrack_ == nullptr && actions_ == nullptr && cp_offset_ == 0 && + characters_preloaded_ == 0 && bound_checked_up_to_ == 0 && + quick_check_performed_.characters() == 0 && at_start_ == UNKNOWN; + } + TriBool at_start() { return at_start_; } + void set_at_start(TriBool at_start) { at_start_ = at_start; } + Label* backtrack() { return backtrack_; } + Label* loop_label() { return loop_label_; } + RegExpNode* stop_node() { return stop_node_; } + int characters_preloaded() { return characters_preloaded_; } + int bound_checked_up_to() { return bound_checked_up_to_; } + int flush_budget() { return flush_budget_; } + QuickCheckDetails* quick_check_performed() { return &quick_check_performed_; } + bool mentions_reg(int reg); + // Returns true if a deferred position store exists to the specified + // register and stores the offset in the out-parameter. Otherwise + // returns false. + bool GetStoredPosition(int reg, int* cp_offset); + // These set methods and AdvanceCurrentPositionInTrace should be used only on + // new traces - the intention is that traces are immutable after creation. + void add_action(DeferredAction* new_action) { + DCHECK(new_action->next_ == nullptr); + new_action->next_ = actions_; + actions_ = new_action; + } + void set_backtrack(Label* backtrack) { backtrack_ = backtrack; } + void set_stop_node(RegExpNode* node) { stop_node_ = node; } + void set_loop_label(Label* label) { loop_label_ = label; } + void set_characters_preloaded(int count) { characters_preloaded_ = count; } + void set_bound_checked_up_to(int to) { bound_checked_up_to_ = to; } + void set_flush_budget(int to) { flush_budget_ = to; } + void set_quick_check_performed(QuickCheckDetails* d) { + quick_check_performed_ = *d; + } + void InvalidateCurrentCharacter(); + void AdvanceCurrentPositionInTrace(int by, RegExpCompiler* compiler); + + private: + int FindAffectedRegisters(DynamicBitSet* affected_registers, Zone* zone); + void PerformDeferredActions(RegExpMacroAssembler* macro, int max_register, + const DynamicBitSet& affected_registers, + DynamicBitSet* registers_to_pop, + DynamicBitSet* registers_to_clear, Zone* zone); + void RestoreAffectedRegisters(RegExpMacroAssembler* macro, int max_register, + const DynamicBitSet& registers_to_pop, + const DynamicBitSet& registers_to_clear); + int cp_offset_; + DeferredAction* actions_; + Label* backtrack_; + RegExpNode* stop_node_; + Label* loop_label_; + int characters_preloaded_; + int bound_checked_up_to_; + QuickCheckDetails quick_check_performed_; + int flush_budget_; + TriBool at_start_; +}; + +class GreedyLoopState { + public: + explicit GreedyLoopState(bool not_at_start); + + Label* label() { return &label_; } + Trace* counter_backtrack_trace() { return &counter_backtrack_trace_; } + + private: + Label label_; + Trace counter_backtrack_trace_; +}; + +struct PreloadState { + static const int kEatsAtLeastNotYetInitialized = -1; + bool preload_is_current_; + bool preload_has_checked_bounds_; + int preload_characters_; + int eats_at_least_; + void init() { eats_at_least_ = kEatsAtLeastNotYetInitialized; } +}; + +// Analysis performs assertion propagation and computes eats_at_least_ values. +// See the comments on AssertionPropagator and EatsAtLeastPropagator for more +// details. +RegExpError AnalyzeRegExp(Isolate* isolate, bool is_one_byte, RegExpFlags flags, + RegExpNode* node); + +class FrequencyCollator { + public: + FrequencyCollator() : total_samples_(0) { + for (int i = 0; i < RegExpMacroAssembler::kTableSize; i++) { + frequencies_[i] = CharacterFrequency(i); + } + } + + void CountCharacter(int character) { + int index = (character & RegExpMacroAssembler::kTableMask); + frequencies_[index].Increment(); + total_samples_++; + } + + // Does not measure in percent, but rather per-128 (the table size from the + // regexp macro assembler). + int Frequency(int in_character) { + DCHECK((in_character & RegExpMacroAssembler::kTableMask) == in_character); + if (total_samples_ < 1) return 1; // Division by zero. + int freq_in_per128 = + (frequencies_[in_character].counter() * 128) / total_samples_; + return freq_in_per128; + } + + private: + class CharacterFrequency { + public: + CharacterFrequency() : counter_(0), character_(-1) {} + explicit CharacterFrequency(int character) + : counter_(0), character_(character) {} + + void Increment() { counter_++; } + int counter() { return counter_; } + int character() { return character_; } + + private: + int counter_; + int character_; + }; + + private: + CharacterFrequency frequencies_[RegExpMacroAssembler::kTableSize]; + int total_samples_; +}; + +class RegExpCompiler { + public: + RegExpCompiler(Isolate* isolate, Zone* zone, int capture_count, + RegExpFlags flags, bool is_one_byte); + + int AllocateRegister() { + if (next_register_ >= RegExpMacroAssembler::kMaxRegister) { + reg_exp_too_big_ = true; + return next_register_; + } + return next_register_++; + } + + // Lookarounds to match lone surrogates for unicode character class matches + // are never nested. We can therefore reuse registers. + int UnicodeLookaroundStackRegister() { + if (unicode_lookaround_stack_register_ == kNoRegister) { + unicode_lookaround_stack_register_ = AllocateRegister(); + } + return unicode_lookaround_stack_register_; + } + + int UnicodeLookaroundPositionRegister() { + if (unicode_lookaround_position_register_ == kNoRegister) { + unicode_lookaround_position_register_ = AllocateRegister(); + } + return unicode_lookaround_position_register_; + } + + struct CompilationResult final { + explicit CompilationResult(RegExpError err) : error(err) {} + CompilationResult(Handle<Object> code, int registers) + : code(code), num_registers(registers) {} + + static CompilationResult RegExpTooBig() { + return CompilationResult(RegExpError::kTooLarge); + } + + bool Succeeded() const { return error == RegExpError::kNone; } + + const RegExpError error = RegExpError::kNone; + Handle<Object> code; + int num_registers = 0; + }; + + CompilationResult Assemble(Isolate* isolate, RegExpMacroAssembler* assembler, + RegExpNode* start, int capture_count, + Handle<String> pattern); + + // Preprocessing is the final step of node creation before analysis + // and assembly. It includes: + // - Wrapping the body of the regexp in capture 0. + // - Inserting the implicit .* before/after the regexp if necessary. + // - If the input is a one-byte string, filtering out nodes that can't match. + // - Fixing up regexp matches that start within a surrogate pair. + RegExpNode* PreprocessRegExp(RegExpCompileData* data, RegExpFlags flags, + bool is_one_byte); + + // If the regexp matching starts within a surrogate pair, step back to the + // lead surrogate and start matching from there. + RegExpNode* OptionallyStepBackToLeadSurrogate(RegExpNode* on_success); + + inline void AddWork(RegExpNode* node) { + if (!node->on_work_list() && !node->label()->is_bound()) { + node->set_on_work_list(true); + work_list_->push_back(node); + } + } + + static const int kImplementationOffset = 0; + static const int kNumberOfRegistersOffset = 0; + static const int kCodeOffset = 1; + + RegExpMacroAssembler* macro_assembler() { return macro_assembler_; } + EndNode* accept() { return accept_; } + + static const int kMaxRecursion = 100; + inline int recursion_depth() { return recursion_depth_; } + inline void IncrementRecursionDepth() { recursion_depth_++; } + inline void DecrementRecursionDepth() { recursion_depth_--; } + + RegExpFlags flags() const { return flags_; } + + void SetRegExpTooBig() { reg_exp_too_big_ = true; } + + inline bool one_byte() { return one_byte_; } + inline bool optimize() { return optimize_; } + inline void set_optimize(bool value) { optimize_ = value; } + inline bool limiting_recursion() { return limiting_recursion_; } + inline void set_limiting_recursion(bool value) { + limiting_recursion_ = value; + } + bool read_backward() { return read_backward_; } + void set_read_backward(bool value) { read_backward_ = value; } + FrequencyCollator* frequency_collator() { return &frequency_collator_; } + + int current_expansion_factor() { return current_expansion_factor_; } + void set_current_expansion_factor(int value) { + current_expansion_factor_ = value; + } + + // The recursive nature of ToNode node generation means we may run into stack + // overflow issues. We introduce periodic checks to detect these, and the + // tick counter helps limit overhead of these checks. + // TODO(jgruber): This is super hacky and should be replaced by an abort + // mechanism or iterative node generation. + void ToNodeMaybeCheckForStackOverflow() { + if ((to_node_overflow_check_ticks_++ % 16 == 0)) { + ToNodeCheckForStackOverflow(); + } + } + void ToNodeCheckForStackOverflow(); + + Isolate* isolate() const { return isolate_; } + Zone* zone() const { return zone_; } + + static const int kNoRegister = -1; + + private: + EndNode* accept_; + int next_register_; + int unicode_lookaround_stack_register_; + int unicode_lookaround_position_register_; + ZoneVector<RegExpNode*>* work_list_; + int recursion_depth_; + const RegExpFlags flags_; + RegExpMacroAssembler* macro_assembler_; + bool one_byte_; + bool reg_exp_too_big_; + bool limiting_recursion_; + int to_node_overflow_check_ticks_ = 0; + bool optimize_; + bool read_backward_; + int current_expansion_factor_; + FrequencyCollator frequency_collator_; + Isolate* isolate_; + Zone* zone_; +}; + +// Categorizes character ranges into BMP, non-BMP, lead, and trail surrogates. +class UnicodeRangeSplitter { + public: + V8_EXPORT_PRIVATE UnicodeRangeSplitter(ZoneList<CharacterRange>* base); + + static constexpr int kInitialSize = 8; + using CharacterRangeVector = base::SmallVector<CharacterRange, kInitialSize>; + + const CharacterRangeVector* bmp() const { return &bmp_; } + const CharacterRangeVector* lead_surrogates() const { + return &lead_surrogates_; + } + const CharacterRangeVector* trail_surrogates() const { + return &trail_surrogates_; + } + const CharacterRangeVector* non_bmp() const { return &non_bmp_; } + + private: + void AddRange(CharacterRange range); + + CharacterRangeVector bmp_; + CharacterRangeVector lead_surrogates_; + CharacterRangeVector trail_surrogates_; + CharacterRangeVector non_bmp_; +}; + +// We need to check for the following characters: 0x39C 0x3BC 0x178. +// TODO(jgruber): Move to CharacterRange. +bool RangeContainsLatin1Equivalents(CharacterRange range); + +} // namespace internal +} // namespace v8 + +#endif // V8_REGEXP_REGEXP_COMPILER_H_ diff --git a/js/src/irregexp/imported/regexp-dotprinter.cc b/js/src/irregexp/imported/regexp-dotprinter.cc new file mode 100644 index 0000000000..6746992a0a --- /dev/null +++ b/js/src/irregexp/imported/regexp-dotprinter.cc @@ -0,0 +1,249 @@ +// 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-dotprinter.h" + +#include "irregexp/imported/regexp-compiler.h" + +namespace v8 { +namespace internal { + +// ------------------------------------------------------------------- +// Dot/dotty output + +class DotPrinterImpl : public NodeVisitor { + public: + explicit DotPrinterImpl(std::ostream& os) : os_(os) {} + void PrintNode(const char* label, RegExpNode* node); + void Visit(RegExpNode* node); + void PrintAttributes(RegExpNode* from); + void PrintOnFailure(RegExpNode* from, RegExpNode* to); +#define DECLARE_VISIT(Type) virtual void Visit##Type(Type##Node* that); + FOR_EACH_NODE_TYPE(DECLARE_VISIT) +#undef DECLARE_VISIT + private: + std::ostream& os_; +}; + +void DotPrinterImpl::PrintNode(const char* label, RegExpNode* node) { + os_ << "digraph G {\n graph [label=\""; + for (int i = 0; label[i]; i++) { + switch (label[i]) { + case '\\': + os_ << "\\\\"; + break; + case '"': + os_ << "\""; + break; + default: + os_ << label[i]; + break; + } + } + os_ << "\"];\n"; + Visit(node); + os_ << "}" << std::endl; +} + +void DotPrinterImpl::Visit(RegExpNode* node) { + if (node->info()->visited) return; + node->info()->visited = true; + node->Accept(this); +} + +void DotPrinterImpl::PrintOnFailure(RegExpNode* from, RegExpNode* on_failure) { + os_ << " n" << from << " -> n" << on_failure << " [style=dotted];\n"; + Visit(on_failure); +} + +class AttributePrinter { + public: + explicit AttributePrinter(std::ostream& os) : os_(os), first_(true) {} + void PrintSeparator() { + if (first_) { + first_ = false; + } else { + os_ << "|"; + } + } + void PrintBit(const char* name, bool value) { + if (!value) return; + PrintSeparator(); + os_ << "{" << name << "}"; + } + void PrintPositive(const char* name, int value) { + if (value < 0) return; + PrintSeparator(); + os_ << "{" << name << "|" << value << "}"; + } + + private: + std::ostream& os_; + bool first_; +}; + +void DotPrinterImpl::PrintAttributes(RegExpNode* that) { + os_ << " a" << that << " [shape=Mrecord, color=grey, fontcolor=grey, " + << "margin=0.1, fontsize=10, label=\"{"; + AttributePrinter printer(os_); + NodeInfo* info = that->info(); + printer.PrintBit("NI", info->follows_newline_interest); + printer.PrintBit("WI", info->follows_word_interest); + printer.PrintBit("SI", info->follows_start_interest); + Label* label = that->label(); + if (label->is_bound()) printer.PrintPositive("@", label->pos()); + os_ << "}\"];\n" + << " a" << that << " -> n" << that + << " [style=dashed, color=grey, arrowhead=none];\n"; +} + +void DotPrinterImpl::VisitChoice(ChoiceNode* that) { + os_ << " n" << that << " [shape=Mrecord, label=\"?\"];\n"; + for (int i = 0; i < that->alternatives()->length(); i++) { + GuardedAlternative alt = that->alternatives()->at(i); + os_ << " n" << that << " -> n" << alt.node(); + } + for (int i = 0; i < that->alternatives()->length(); i++) { + GuardedAlternative alt = that->alternatives()->at(i); + alt.node()->Accept(this); + } +} + +void DotPrinterImpl::VisitLoopChoice(LoopChoiceNode* that) { + VisitChoice(that); +} + +void DotPrinterImpl::VisitNegativeLookaroundChoice( + NegativeLookaroundChoiceNode* that) { + VisitChoice(that); +} + +void DotPrinterImpl::VisitText(TextNode* that) { + Zone* zone = that->zone(); + os_ << " n" << that << " [label=\""; + for (int i = 0; i < that->elements()->length(); i++) { + if (i > 0) os_ << " "; + TextElement elm = that->elements()->at(i); + switch (elm.text_type()) { + case TextElement::ATOM: { + base::Vector<const base::uc16> data = elm.atom()->data(); + for (int j = 0; j < data.length(); j++) { + os_ << static_cast<char>(data[j]); + } + break; + } + case TextElement::CLASS_RANGES: { + RegExpClassRanges* node = elm.class_ranges(); + os_ << "["; + if (node->is_negated()) os_ << "^"; + for (int j = 0; j < node->ranges(zone)->length(); j++) { + CharacterRange range = node->ranges(zone)->at(j); + os_ << AsUC32(range.from()) << "-" << AsUC32(range.to()); + } + os_ << "]"; + break; + } + default: + UNREACHABLE(); + } + } + os_ << "\", shape=box, peripheries=2];\n"; + PrintAttributes(that); + os_ << " n" << that << " -> n" << that->on_success() << ";\n"; + Visit(that->on_success()); +} + +void DotPrinterImpl::VisitBackReference(BackReferenceNode* that) { + os_ << " n" << that << " [label=\"$" << that->start_register() << "..$" + << that->end_register() << "\", shape=doubleoctagon];\n"; + PrintAttributes(that); + os_ << " n" << that << " -> n" << that->on_success() << ";\n"; + Visit(that->on_success()); +} + +void DotPrinterImpl::VisitEnd(EndNode* that) { + os_ << " n" << that << " [style=bold, shape=point];\n"; + PrintAttributes(that); +} + +void DotPrinterImpl::VisitAssertion(AssertionNode* that) { + os_ << " n" << that << " ["; + switch (that->assertion_type()) { + case AssertionNode::AT_END: + os_ << "label=\"$\", shape=septagon"; + break; + case AssertionNode::AT_START: + os_ << "label=\"^\", shape=septagon"; + break; + case AssertionNode::AT_BOUNDARY: + os_ << "label=\"\\b\", shape=septagon"; + break; + case AssertionNode::AT_NON_BOUNDARY: + os_ << "label=\"\\B\", shape=septagon"; + break; + case AssertionNode::AFTER_NEWLINE: + os_ << "label=\"(?<=\\n)\", shape=septagon"; + break; + } + os_ << "];\n"; + PrintAttributes(that); + RegExpNode* successor = that->on_success(); + os_ << " n" << that << " -> n" << successor << ";\n"; + Visit(successor); +} + +void DotPrinterImpl::VisitAction(ActionNode* that) { + os_ << " n" << that << " ["; + switch (that->action_type_) { + case ActionNode::SET_REGISTER_FOR_LOOP: + os_ << "label=\"$" << that->data_.u_store_register.reg + << ":=" << that->data_.u_store_register.value << "\", shape=octagon"; + break; + case ActionNode::INCREMENT_REGISTER: + os_ << "label=\"$" << that->data_.u_increment_register.reg + << "++\", shape=octagon"; + break; + case ActionNode::STORE_POSITION: + os_ << "label=\"$" << that->data_.u_position_register.reg + << ":=$pos\", shape=octagon"; + break; + case ActionNode::BEGIN_POSITIVE_SUBMATCH: + os_ << "label=\"$" << that->data_.u_submatch.current_position_register + << ":=$pos,begin-positive\", shape=septagon"; + break; + case ActionNode::BEGIN_NEGATIVE_SUBMATCH: + os_ << "label=\"$" << that->data_.u_submatch.current_position_register + << ":=$pos,begin-negative\", shape=septagon"; + break; + case ActionNode::POSITIVE_SUBMATCH_SUCCESS: + os_ << "label=\"escape\", shape=septagon"; + break; + case ActionNode::EMPTY_MATCH_CHECK: + os_ << "label=\"$" << that->data_.u_empty_match_check.start_register + << "=$pos?,$" << that->data_.u_empty_match_check.repetition_register + << "<" << that->data_.u_empty_match_check.repetition_limit + << "?\", shape=septagon"; + break; + case ActionNode::CLEAR_CAPTURES: { + os_ << "label=\"clear $" << that->data_.u_clear_captures.range_from + << " to $" << that->data_.u_clear_captures.range_to + << "\", shape=septagon"; + break; + } + } + os_ << "];\n"; + PrintAttributes(that); + RegExpNode* successor = that->on_success(); + os_ << " n" << that << " -> n" << successor << ";\n"; + Visit(successor); +} + +void DotPrinter::DotPrint(const char* label, RegExpNode* node) { + StdoutStream os; + DotPrinterImpl printer(os); + printer.PrintNode(label, node); +} + +} // namespace internal +} // namespace v8 diff --git a/js/src/irregexp/imported/regexp-dotprinter.h b/js/src/irregexp/imported/regexp-dotprinter.h new file mode 100644 index 0000000000..7fcece6e1a --- /dev/null +++ b/js/src/irregexp/imported/regexp-dotprinter.h @@ -0,0 +1,23 @@ +// 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. + +#ifndef V8_REGEXP_REGEXP_DOTPRINTER_H_ +#define V8_REGEXP_REGEXP_DOTPRINTER_H_ + +#include "irregexp/RegExpShim.h" + +namespace v8 { +namespace internal { + +class RegExpNode; + +class DotPrinter final : public AllStatic { + public: + static void DotPrint(const char* label, RegExpNode* node); +}; + +} // namespace internal +} // namespace v8 + +#endif // V8_REGEXP_REGEXP_DOTPRINTER_H_ diff --git a/js/src/irregexp/imported/regexp-error.cc b/js/src/irregexp/imported/regexp-error.cc new file mode 100644 index 0000000000..d0b4c263a4 --- /dev/null +++ b/js/src/irregexp/imported/regexp-error.cc @@ -0,0 +1,22 @@ +// Copyright 2020 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-error.h" + +namespace v8 { +namespace internal { + +const char* const kRegExpErrorStrings[] = { +#define TEMPLATE(NAME, STRING) STRING, + REGEXP_ERROR_MESSAGES(TEMPLATE) +#undef TEMPLATE +}; + +const char* RegExpErrorString(RegExpError error) { + DCHECK_LT(error, RegExpError::NumErrors); + return kRegExpErrorStrings[static_cast<int>(error)]; +} + +} // namespace internal +} // namespace v8 diff --git a/js/src/irregexp/imported/regexp-error.h b/js/src/irregexp/imported/regexp-error.h new file mode 100644 index 0000000000..ff4fe41cd5 --- /dev/null +++ b/js/src/irregexp/imported/regexp-error.h @@ -0,0 +1,67 @@ +// Copyright 2020 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. + +#ifndef V8_REGEXP_REGEXP_ERROR_H_ +#define V8_REGEXP_REGEXP_ERROR_H_ + +#include "irregexp/RegExpShim.h" + +namespace v8 { +namespace internal { + +#define REGEXP_ERROR_MESSAGES(T) \ + T(None, "") \ + T(StackOverflow, "Maximum call stack size exceeded") \ + T(AnalysisStackOverflow, "Stack overflow") \ + T(TooLarge, "Regular expression too large") \ + T(UnterminatedGroup, "Unterminated group") \ + T(UnmatchedParen, "Unmatched ')'") \ + T(EscapeAtEndOfPattern, "\\ at end of pattern") \ + T(InvalidPropertyName, "Invalid property name") \ + T(InvalidEscape, "Invalid escape") \ + T(InvalidDecimalEscape, "Invalid decimal escape") \ + T(InvalidUnicodeEscape, "Invalid Unicode escape") \ + T(NothingToRepeat, "Nothing to repeat") \ + T(LoneQuantifierBrackets, "Lone quantifier brackets") \ + T(RangeOutOfOrder, "numbers out of order in {} quantifier") \ + T(IncompleteQuantifier, "Incomplete quantifier") \ + T(InvalidQuantifier, "Invalid quantifier") \ + T(InvalidGroup, "Invalid group") \ + T(MultipleFlagDashes, "Multiple dashes in flag group") \ + T(NotLinear, "Cannot be executed in linear time") \ + T(RepeatedFlag, "Repeated flag in flag group") \ + T(InvalidFlagGroup, "Invalid flag group") \ + T(TooManyCaptures, "Too many captures") \ + T(InvalidCaptureGroupName, "Invalid capture group name") \ + T(DuplicateCaptureGroupName, "Duplicate capture group name") \ + T(InvalidNamedReference, "Invalid named reference") \ + T(InvalidNamedCaptureReference, "Invalid named capture referenced") \ + T(InvalidClassEscape, "Invalid class escape") \ + T(InvalidClassPropertyName, "Invalid property name in character class") \ + T(InvalidCharacterClass, "Invalid character class") \ + T(UnterminatedCharacterClass, "Unterminated character class") \ + T(OutOfOrderCharacterClass, "Range out of order in character class") \ + T(InvalidClassSetOperation, "Invalid set operation in character class") \ + T(InvalidCharacterInClass, "Invalid character in character class") \ + T(NegatedCharacterClassWithStrings, \ + "Negated character class may contain strings") + +enum class RegExpError : uint32_t { +#define TEMPLATE(NAME, STRING) k##NAME, + REGEXP_ERROR_MESSAGES(TEMPLATE) +#undef TEMPLATE + NumErrors +}; + +V8_EXPORT_PRIVATE const char* RegExpErrorString(RegExpError error); + +inline constexpr bool RegExpErrorIsStackOverflow(RegExpError error) { + return error == RegExpError::kStackOverflow || + error == RegExpError::kAnalysisStackOverflow; +} + +} // namespace internal +} // namespace v8 + +#endif // V8_REGEXP_REGEXP_ERROR_H_ diff --git a/js/src/irregexp/imported/regexp-interpreter.cc b/js/src/irregexp/imported/regexp-interpreter.cc new file mode 100644 index 0000000000..43c8a4a5a4 --- /dev/null +++ b/js/src/irregexp/imported/regexp-interpreter.cc @@ -0,0 +1,1149 @@ +// Copyright 2011 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. + +// A simple interpreter for the Irregexp byte code. + +#include "irregexp/imported/regexp-interpreter.h" + +#include "irregexp/imported/regexp-bytecodes.h" +#include "irregexp/imported/regexp-macro-assembler.h" +#include "irregexp/imported/regexp-stack.h" // For kMaximumStackSize. +#include "irregexp/imported/regexp.h" + +#ifdef V8_INTL_SUPPORT +#include "unicode/uchar.h" +#endif // V8_INTL_SUPPORT + +// Use token threaded dispatch iff the compiler supports computed gotos and the +// build argument v8_enable_regexp_interpreter_threaded_dispatch was set. +#if V8_HAS_COMPUTED_GOTO && \ + defined(V8_ENABLE_REGEXP_INTERPRETER_THREADED_DISPATCH) +#define V8_USE_COMPUTED_GOTO 1 +#endif // V8_HAS_COMPUTED_GOTO + +namespace v8 { +namespace internal { + +namespace { + +bool BackRefMatchesNoCase(Isolate* isolate, int from, int current, int len, + base::Vector<const base::uc16> subject, + bool unicode) { + Address offset_a = + reinterpret_cast<Address>(const_cast<base::uc16*>(&subject.at(from))); + Address offset_b = + reinterpret_cast<Address>(const_cast<base::uc16*>(&subject.at(current))); + size_t length = len * base::kUC16Size; + + bool result = unicode + ? RegExpMacroAssembler::CaseInsensitiveCompareUnicode( + offset_a, offset_b, length, isolate) + : RegExpMacroAssembler::CaseInsensitiveCompareNonUnicode( + offset_a, offset_b, length, isolate); + return result == 1; +} + +bool BackRefMatchesNoCase(Isolate* isolate, int from, int current, int len, + base::Vector<const uint8_t> subject, bool unicode) { + // For Latin1 characters the unicode flag makes no difference. + for (int i = 0; i < len; i++) { + unsigned int old_char = subject[from++]; + unsigned int new_char = subject[current++]; + if (old_char == new_char) continue; + // Convert both characters to lower case. + old_char |= 0x20; + new_char |= 0x20; + if (old_char != new_char) return false; + // Not letters in the ASCII range and Latin-1 range. + if (!(old_char - 'a' <= 'z' - 'a') && + !(old_char - 224 <= 254 - 224 && old_char != 247)) { + return false; + } + } + return true; +} + +#ifdef DEBUG +void MaybeTraceInterpreter(const uint8_t* code_base, const uint8_t* pc, + int stack_depth, int current_position, + uint32_t current_char, int bytecode_length, + const char* bytecode_name) { + if (v8_flags.trace_regexp_bytecodes) { + const bool printable = std::isprint(current_char); + const char* format = + printable + ? "pc = %02x, sp = %d, curpos = %d, curchar = %08x (%c), bc = " + : "pc = %02x, sp = %d, curpos = %d, curchar = %08x .%c., bc = "; + PrintF(format, pc - code_base, stack_depth, current_position, current_char, + printable ? current_char : '.'); + + RegExpBytecodeDisassembleSingle(code_base, pc); + } +} +#endif // DEBUG + +int32_t Load32Aligned(const uint8_t* pc) { + DCHECK_EQ(0, reinterpret_cast<intptr_t>(pc) & 3); + return *reinterpret_cast<const int32_t*>(pc); +} + +// TODO(jgruber): Rename to Load16AlignedUnsigned. +uint32_t Load16Aligned(const uint8_t* pc) { + DCHECK_EQ(0, reinterpret_cast<intptr_t>(pc) & 1); + return *reinterpret_cast<const uint16_t*>(pc); +} + +int32_t Load16AlignedSigned(const uint8_t* pc) { + DCHECK_EQ(0, reinterpret_cast<intptr_t>(pc) & 1); + return *reinterpret_cast<const int16_t*>(pc); +} + +// Helpers to access the packed argument. Takes the 32 bits containing the +// current bytecode, where the 8 LSB contain the bytecode and the rest contains +// a packed 24-bit argument. +// TODO(jgruber): Specify signed-ness in bytecode signature declarations, and +// police restrictions during bytecode generation. +int32_t LoadPacked24Signed(int32_t bytecode_and_packed_arg) { + return bytecode_and_packed_arg >> BYTECODE_SHIFT; +} +uint32_t LoadPacked24Unsigned(int32_t bytecode_and_packed_arg) { + return static_cast<uint32_t>(bytecode_and_packed_arg) >> BYTECODE_SHIFT; +} + +// A simple abstraction over the backtracking stack used by the interpreter. +// +// Despite the name 'backtracking' stack, it's actually used as a generic stack +// that stores both program counters (= offsets into the bytecode) and generic +// integer values. +class BacktrackStack { + public: + BacktrackStack() = default; + BacktrackStack(const BacktrackStack&) = delete; + BacktrackStack& operator=(const BacktrackStack&) = delete; + + V8_WARN_UNUSED_RESULT bool push(int v) { + data_.emplace_back(v); + return (static_cast<int>(data_.size()) <= kMaxSize); + } + int peek() const { + DCHECK(!data_.empty()); + return data_.back(); + } + int pop() { + int v = peek(); + data_.pop_back(); + return v; + } + + // The 'sp' is the index of the first empty element in the stack. + int sp() const { return static_cast<int>(data_.size()); } + void set_sp(int new_sp) { + DCHECK_LE(new_sp, sp()); + data_.resize_no_init(new_sp); + } + + private: + // Semi-arbitrary. Should be large enough for common cases to remain in the + // static stack-allocated backing store, but small enough not to waste space. + static constexpr int kStaticCapacity = 64; + + using ValueT = int; + base::SmallVector<ValueT, kStaticCapacity> data_; + + static constexpr int kMaxSize = + RegExpStack::kMaximumStackSize / sizeof(ValueT); +}; + +// Registers used during interpreter execution. These consist of output +// registers in indices [0, output_register_count[ which will contain matcher +// results as a {start,end} index tuple for each capture (where the whole match +// counts as implicit capture 0); and internal registers in indices +// [output_register_count, total_register_count[. +class InterpreterRegisters { + public: + using RegisterT = int; + + InterpreterRegisters(int total_register_count, RegisterT* output_registers, + int output_register_count) + : registers_(total_register_count), + output_registers_(output_registers), + output_register_count_(output_register_count) { + // TODO(jgruber): Use int32_t consistently for registers. Currently, CSA + // uses int32_t while runtime uses int. + static_assert(sizeof(int) == sizeof(int32_t)); + DCHECK_GE(output_register_count, 2); // At least 2 for the match itself. + DCHECK_GE(total_register_count, output_register_count); + DCHECK_LE(total_register_count, RegExpMacroAssembler::kMaxRegisterCount); + DCHECK_NOT_NULL(output_registers); + + // Initialize the output register region to -1 signifying 'no match'. + std::memset(registers_.data(), -1, + output_register_count * sizeof(RegisterT)); + } + + const RegisterT& operator[](size_t index) const { return registers_[index]; } + RegisterT& operator[](size_t index) { return registers_[index]; } + + void CopyToOutputRegisters() { + MemCopy(output_registers_, registers_.data(), + output_register_count_ * sizeof(RegisterT)); + } + + private: + static constexpr int kStaticCapacity = 64; // Arbitrary. + base::SmallVector<RegisterT, kStaticCapacity> registers_; + RegisterT* const output_registers_; + const int output_register_count_; +}; + +IrregexpInterpreter::Result ThrowStackOverflow(Isolate* isolate, + RegExp::CallOrigin call_origin) { + CHECK(call_origin == RegExp::CallOrigin::kFromRuntime); + // We abort interpreter execution after the stack overflow is thrown, and thus + // allow allocation here despite the outer DisallowGarbageCollectionScope. + AllowGarbageCollection yes_gc; + isolate->StackOverflow(); + return IrregexpInterpreter::EXCEPTION; +} + +// Only throws if called from the runtime, otherwise just returns the EXCEPTION +// status code. +IrregexpInterpreter::Result MaybeThrowStackOverflow( + Isolate* isolate, RegExp::CallOrigin call_origin) { + if (call_origin == RegExp::CallOrigin::kFromRuntime) { + return ThrowStackOverflow(isolate, call_origin); + } else { + return IrregexpInterpreter::EXCEPTION; + } +} + +template <typename Char> +void UpdateCodeAndSubjectReferences( + Isolate* isolate, Handle<ByteArray> code_array, + Handle<String> subject_string, ByteArray* code_array_out, + const uint8_t** code_base_out, const uint8_t** pc_out, + String* subject_string_out, + base::Vector<const Char>* subject_string_vector_out) { + DisallowGarbageCollection no_gc; + + if (*code_base_out != code_array->GetDataStartAddress()) { + *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(); + *pc_out = *code_base_out + pc_offset; + } + + DCHECK(subject_string->IsFlat()); + *subject_string_out = *subject_string; + *subject_string_vector_out = subject_string->GetCharVector<Char>(no_gc); +} + +// Runs all pending interrupts and updates unhandlified object references if +// necessary. +template <typename Char> +IrregexpInterpreter::Result HandleInterrupts( + Isolate* isolate, RegExp::CallOrigin call_origin, ByteArray* code_array_out, + String* subject_string_out, const uint8_t** code_base_out, + base::Vector<const Char>* subject_string_vector_out, + const uint8_t** pc_out) { + DisallowGarbageCollection no_gc; + + StackLimitCheck check(isolate); + bool js_has_overflowed = check.JsHasOverflowed(); + + 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. + // 2. The stack guard was used to interrupt execution for another purpose, + // forcing the call through the runtime system. + if (js_has_overflowed) { + return IrregexpInterpreter::EXCEPTION; + } else if (check.InterruptRequested()) { + return IrregexpInterpreter::RETRY; + } + } else { + DCHECK(call_origin == RegExp::CallOrigin::kFromRuntime); + // Prepare for possible GC. + HandleScope handles(isolate); + Handle<ByteArray> code_handle(*code_array_out, isolate); + Handle<String> subject_handle(*subject_string_out, isolate); + + 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; + { + AllowGarbageCollection yes_gc; + result = isolate->stack_guard()->HandleInterrupts(); + } + if (result.IsException(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 + // RawMatch. + if (String::IsOneByteRepresentationUnderneath(*subject_handle) != + was_one_byte) { + return IrregexpInterpreter::RETRY; + } + + UpdateCodeAndSubjectReferences( + isolate, code_handle, subject_handle, code_array_out, code_base_out, + pc_out, subject_string_out, subject_string_vector_out); + } + } + + return IrregexpInterpreter::SUCCESS; +} + +bool CheckBitInTable(const uint32_t current_char, const uint8_t* const table) { + int mask = RegExpMacroAssembler::kTableMask; + int b = table[(current_char & mask) >> kBitsPerByteLog2]; + int bit = (current_char & (kBitsPerByte - 1)); + return (b & (1 << bit)) != 0; +} + +// Returns true iff 0 <= index < length. +bool IndexIsInBounds(int index, int length) { + DCHECK_GE(length, 0); + return static_cast<uintptr_t>(index) < static_cast<uintptr_t>(length); +} + +// If computed gotos are supported by the compiler, we can get addresses to +// labels directly in C/C++. Every bytecode handler has its own label and we +// store the addresses in a dispatch table indexed by bytecode. To execute the +// next handler we simply jump (goto) directly to its address. +#if V8_USE_COMPUTED_GOTO +#define BC_LABEL(name) BC_##name: +#define DECODE() \ + do { \ + next_insn = Load32Aligned(next_pc); \ + next_handler_addr = dispatch_table[next_insn & BYTECODE_MASK]; \ + } while (false) +#define DISPATCH() \ + pc = next_pc; \ + insn = next_insn; \ + goto* next_handler_addr +// Without computed goto support, we fall back to a simple switch-based +// dispatch (A large switch statement inside a loop with a case for every +// bytecode). +#else // V8_USE_COMPUTED_GOTO +#define BC_LABEL(name) case BC_##name: +#define DECODE() next_insn = Load32Aligned(next_pc) +#define DISPATCH() \ + pc = next_pc; \ + insn = next_insn; \ + goto switch_dispatch_continuation +#endif // V8_USE_COMPUTED_GOTO + +// ADVANCE/SET_PC_FROM_OFFSET are separated from DISPATCH, because ideally some +// instructions can be executed between ADVANCE/SET_PC_FROM_OFFSET and DISPATCH. +// We want those two macros as far apart as possible, because the goto in +// DISPATCH is dependent on a memory load in ADVANCE/SET_PC_FROM_OFFSET. If we +// don't hit the cache and have to fetch the next handler address from physical +// memory, instructions between ADVANCE/SET_PC_FROM_OFFSET and DISPATCH can +// potentially be executed unconditionally, reducing memory stall. +#define ADVANCE(name) \ + next_pc = pc + RegExpBytecodeLength(BC_##name); \ + DECODE() +#define SET_PC_FROM_OFFSET(offset) \ + next_pc = code_base + offset; \ + DECODE() + +// Current position mutations. +#define SET_CURRENT_POSITION(value) \ + do { \ + current = (value); \ + DCHECK(base::IsInRange(current, 0, subject.length())); \ + } while (false) +#define ADVANCE_CURRENT_POSITION(by) SET_CURRENT_POSITION(current + (by)) + +#ifdef DEBUG +#define BYTECODE(name) \ + BC_LABEL(name) \ + MaybeTraceInterpreter(code_base, pc, backtrack_stack.sp(), current, \ + current_char, RegExpBytecodeLength(BC_##name), #name); +#else +#define BYTECODE(name) BC_LABEL(name) +#endif // DEBUG + +template <typename Char> +IrregexpInterpreter::Result RawMatch( + Isolate* isolate, ByteArray code_array, String subject_string, + base::Vector<const Char> 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 + +// We have to make sure that no OOB access to the dispatch table is possible and +// all values are valid label addresses. +// Otherwise jumps to arbitrary addresses could potentially happen. +// This is ensured as follows: +// Every index to the dispatch table gets masked using BYTECODE_MASK in +// DECODE(). This way we can only get values between 0 (only the least +// significant byte of an integer is used) and kRegExpPaddedBytecodeCount - 1 +// (BYTECODE_MASK is defined to be exactly this value). +// All entries from kRegExpBytecodeCount to kRegExpPaddedBytecodeCount have to +// be filled with BREAKs (invalid operation). + +// Fill dispatch table from last defined bytecode up to the next power of two +// with BREAK (invalid operation). +// TODO(pthier): Find a way to fill up automatically (at compile time) +// 59 real bytecodes -> 5 fillers +#define BYTECODE_FILLER_ITERATOR(V) \ + V(BREAK) /* 1 */ \ + V(BREAK) /* 2 */ \ + V(BREAK) /* 3 */ \ + V(BREAK) /* 4 */ \ + V(BREAK) /* 5 */ + +#define COUNT(...) +1 + static constexpr int kRegExpBytecodeFillerCount = + BYTECODE_FILLER_ITERATOR(COUNT); +#undef COUNT + + // Make sure kRegExpPaddedBytecodeCount is actually the closest possible power + // of two. + DCHECK_EQ(kRegExpPaddedBytecodeCount, + base::bits::RoundUpToPowerOfTwo32(kRegExpBytecodeCount)); + + // Make sure every bytecode we get by using BYTECODE_MASK is well defined. + static_assert(kRegExpBytecodeCount <= kRegExpPaddedBytecodeCount); + static_assert(kRegExpBytecodeCount + kRegExpBytecodeFillerCount == + kRegExpPaddedBytecodeCount); + +#define DECLARE_DISPATCH_TABLE_ENTRY(name, ...) &&BC_##name, + static const void* const dispatch_table[kRegExpPaddedBytecodeCount] = { + BYTECODE_ITERATOR(DECLARE_DISPATCH_TABLE_ENTRY) + BYTECODE_FILLER_ITERATOR(DECLARE_DISPATCH_TABLE_ENTRY)}; +#undef DECLARE_DISPATCH_TABLE_ENTRY +#undef BYTECODE_FILLER_ITERATOR + +#endif // V8_USE_COMPUTED_GOTO + + const uint8_t* pc = code_array.GetDataStartAddress(); + const uint8_t* code_base = pc; + + InterpreterRegisters registers(total_register_count, output_registers, + output_register_count); + BacktrackStack backtrack_stack; + + uint32_t backtrack_count = 0; + +#ifdef DEBUG + if (v8_flags.trace_regexp_bytecodes) { + PrintF("\n\nStart bytecode interpreter\n\n"); + } +#endif + + while (true) { + const uint8_t* next_pc = pc; + int32_t insn; + int32_t next_insn; +#if V8_USE_COMPUTED_GOTO + const void* next_handler_addr; + DECODE(); + DISPATCH(); +#else + insn = Load32Aligned(pc); + switch (insn & BYTECODE_MASK) { +#endif // V8_USE_COMPUTED_GOTO + BYTECODE(BREAK) { UNREACHABLE(); } + BYTECODE(PUSH_CP) { + ADVANCE(PUSH_CP); + if (!backtrack_stack.push(current)) { + return MaybeThrowStackOverflow(isolate, call_origin); + } + DISPATCH(); + } + BYTECODE(PUSH_BT) { + ADVANCE(PUSH_BT); + if (!backtrack_stack.push(Load32Aligned(pc + 4))) { + return MaybeThrowStackOverflow(isolate, call_origin); + } + DISPATCH(); + } + BYTECODE(PUSH_REGISTER) { + ADVANCE(PUSH_REGISTER); + if (!backtrack_stack.push(registers[LoadPacked24Unsigned(insn)])) { + return MaybeThrowStackOverflow(isolate, call_origin); + } + DISPATCH(); + } + BYTECODE(SET_REGISTER) { + ADVANCE(SET_REGISTER); + registers[LoadPacked24Unsigned(insn)] = Load32Aligned(pc + 4); + DISPATCH(); + } + BYTECODE(ADVANCE_REGISTER) { + ADVANCE(ADVANCE_REGISTER); + registers[LoadPacked24Unsigned(insn)] += Load32Aligned(pc + 4); + DISPATCH(); + } + BYTECODE(SET_REGISTER_TO_CP) { + ADVANCE(SET_REGISTER_TO_CP); + registers[LoadPacked24Unsigned(insn)] = current + Load32Aligned(pc + 4); + DISPATCH(); + } + BYTECODE(SET_CP_TO_REGISTER) { + ADVANCE(SET_CP_TO_REGISTER); + SET_CURRENT_POSITION(registers[LoadPacked24Unsigned(insn)]); + DISPATCH(); + } + BYTECODE(SET_REGISTER_TO_SP) { + ADVANCE(SET_REGISTER_TO_SP); + registers[LoadPacked24Unsigned(insn)] = backtrack_stack.sp(); + DISPATCH(); + } + BYTECODE(SET_SP_TO_REGISTER) { + ADVANCE(SET_SP_TO_REGISTER); + backtrack_stack.set_sp(registers[LoadPacked24Unsigned(insn)]); + DISPATCH(); + } + BYTECODE(POP_CP) { + ADVANCE(POP_CP); + SET_CURRENT_POSITION(backtrack_stack.pop()); + DISPATCH(); + } + BYTECODE(POP_BT) { + static_assert(JSRegExp::kNoBacktrackLimit == 0); + if (++backtrack_count == backtrack_limit) { + int return_code = LoadPacked24Signed(insn); + return static_cast<IrregexpInterpreter::Result>(return_code); + } + + IrregexpInterpreter::Result return_code = + HandleInterrupts(isolate, call_origin, &code_array, &subject_string, + &code_base, &subject, &pc); + if (return_code != IrregexpInterpreter::SUCCESS) return return_code; + + SET_PC_FROM_OFFSET(backtrack_stack.pop()); + DISPATCH(); + } + BYTECODE(POP_REGISTER) { + ADVANCE(POP_REGISTER); + registers[LoadPacked24Unsigned(insn)] = backtrack_stack.pop(); + DISPATCH(); + } + BYTECODE(FAIL) { + isolate->counters()->regexp_backtracks()->AddSample( + static_cast<int>(backtrack_count)); + return IrregexpInterpreter::FAILURE; + } + BYTECODE(SUCCEED) { + isolate->counters()->regexp_backtracks()->AddSample( + static_cast<int>(backtrack_count)); + registers.CopyToOutputRegisters(); + return IrregexpInterpreter::SUCCESS; + } + BYTECODE(ADVANCE_CP) { + ADVANCE(ADVANCE_CP); + ADVANCE_CURRENT_POSITION(LoadPacked24Signed(insn)); + DISPATCH(); + } + BYTECODE(GOTO) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + DISPATCH(); + } + BYTECODE(ADVANCE_CP_AND_GOTO) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + ADVANCE_CURRENT_POSITION(LoadPacked24Signed(insn)); + DISPATCH(); + } + BYTECODE(CHECK_GREEDY) { + if (current == backtrack_stack.peek()) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + backtrack_stack.pop(); + } else { + ADVANCE(CHECK_GREEDY); + } + DISPATCH(); + } + BYTECODE(LOAD_CURRENT_CHAR) { + int pos = current + LoadPacked24Signed(insn); + if (pos >= subject.length() || pos < 0) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + } else { + ADVANCE(LOAD_CURRENT_CHAR); + current_char = subject[pos]; + } + DISPATCH(); + } + BYTECODE(LOAD_CURRENT_CHAR_UNCHECKED) { + ADVANCE(LOAD_CURRENT_CHAR_UNCHECKED); + int pos = current + LoadPacked24Signed(insn); + current_char = subject[pos]; + DISPATCH(); + } + BYTECODE(LOAD_2_CURRENT_CHARS) { + int pos = current + LoadPacked24Signed(insn); + if (pos + 2 > subject.length() || pos < 0) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + } else { + ADVANCE(LOAD_2_CURRENT_CHARS); + Char next = subject[pos + 1]; + current_char = (subject[pos] | (next << (kBitsPerByte * sizeof(Char)))); + } + DISPATCH(); + } + BYTECODE(LOAD_2_CURRENT_CHARS_UNCHECKED) { + ADVANCE(LOAD_2_CURRENT_CHARS_UNCHECKED); + int pos = current + LoadPacked24Signed(insn); + Char next = subject[pos + 1]; + current_char = (subject[pos] | (next << (kBitsPerByte * sizeof(Char)))); + DISPATCH(); + } + BYTECODE(LOAD_4_CURRENT_CHARS) { + DCHECK_EQ(1, sizeof(Char)); + int pos = current + LoadPacked24Signed(insn); + if (pos + 4 > subject.length() || pos < 0) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + } else { + ADVANCE(LOAD_4_CURRENT_CHARS); + Char next1 = subject[pos + 1]; + Char next2 = subject[pos + 2]; + Char next3 = subject[pos + 3]; + current_char = + (subject[pos] | (next1 << 8) | (next2 << 16) | (next3 << 24)); + } + DISPATCH(); + } + BYTECODE(LOAD_4_CURRENT_CHARS_UNCHECKED) { + ADVANCE(LOAD_4_CURRENT_CHARS_UNCHECKED); + DCHECK_EQ(1, sizeof(Char)); + int pos = current + LoadPacked24Signed(insn); + Char next1 = subject[pos + 1]; + Char next2 = subject[pos + 2]; + Char next3 = subject[pos + 3]; + current_char = + (subject[pos] | (next1 << 8) | (next2 << 16) | (next3 << 24)); + DISPATCH(); + } + BYTECODE(CHECK_4_CHARS) { + uint32_t c = Load32Aligned(pc + 4); + if (c == current_char) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 8)); + } else { + ADVANCE(CHECK_4_CHARS); + } + DISPATCH(); + } + BYTECODE(CHECK_CHAR) { + uint32_t c = LoadPacked24Unsigned(insn); + if (c == current_char) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + } else { + ADVANCE(CHECK_CHAR); + } + DISPATCH(); + } + BYTECODE(CHECK_NOT_4_CHARS) { + uint32_t c = Load32Aligned(pc + 4); + if (c != current_char) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 8)); + } else { + ADVANCE(CHECK_NOT_4_CHARS); + } + DISPATCH(); + } + BYTECODE(CHECK_NOT_CHAR) { + uint32_t c = LoadPacked24Unsigned(insn); + if (c != current_char) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + } else { + ADVANCE(CHECK_NOT_CHAR); + } + DISPATCH(); + } + BYTECODE(AND_CHECK_4_CHARS) { + uint32_t c = Load32Aligned(pc + 4); + if (c == (current_char & Load32Aligned(pc + 8))) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 12)); + } else { + ADVANCE(AND_CHECK_4_CHARS); + } + DISPATCH(); + } + BYTECODE(AND_CHECK_CHAR) { + uint32_t c = LoadPacked24Unsigned(insn); + if (c == (current_char & Load32Aligned(pc + 4))) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 8)); + } else { + ADVANCE(AND_CHECK_CHAR); + } + DISPATCH(); + } + BYTECODE(AND_CHECK_NOT_4_CHARS) { + uint32_t c = Load32Aligned(pc + 4); + if (c != (current_char & Load32Aligned(pc + 8))) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 12)); + } else { + ADVANCE(AND_CHECK_NOT_4_CHARS); + } + DISPATCH(); + } + BYTECODE(AND_CHECK_NOT_CHAR) { + uint32_t c = LoadPacked24Unsigned(insn); + if (c != (current_char & Load32Aligned(pc + 4))) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 8)); + } else { + ADVANCE(AND_CHECK_NOT_CHAR); + } + DISPATCH(); + } + BYTECODE(MINUS_AND_CHECK_NOT_CHAR) { + uint32_t c = LoadPacked24Unsigned(insn); + uint32_t minus = Load16Aligned(pc + 4); + uint32_t mask = Load16Aligned(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); + 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); + if (from > current_char || current_char > to) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 8)); + } else { + ADVANCE(CHECK_CHAR_NOT_IN_RANGE); + } + DISPATCH(); + } + BYTECODE(CHECK_BIT_IN_TABLE) { + if (CheckBitInTable(current_char, pc + 8)) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + } else { + ADVANCE(CHECK_BIT_IN_TABLE); + } + DISPATCH(); + } + BYTECODE(CHECK_LT) { + uint32_t limit = LoadPacked24Unsigned(insn); + if (current_char < limit) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + } else { + ADVANCE(CHECK_LT); + } + DISPATCH(); + } + BYTECODE(CHECK_GT) { + uint32_t limit = LoadPacked24Unsigned(insn); + if (current_char > limit) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + } else { + ADVANCE(CHECK_GT); + } + DISPATCH(); + } + BYTECODE(CHECK_REGISTER_LT) { + if (registers[LoadPacked24Unsigned(insn)] < Load32Aligned(pc + 4)) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 8)); + } else { + ADVANCE(CHECK_REGISTER_LT); + } + DISPATCH(); + } + BYTECODE(CHECK_REGISTER_GE) { + if (registers[LoadPacked24Unsigned(insn)] >= Load32Aligned(pc + 4)) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 8)); + } else { + ADVANCE(CHECK_REGISTER_GE); + } + DISPATCH(); + } + BYTECODE(CHECK_REGISTER_EQ_POS) { + if (registers[LoadPacked24Unsigned(insn)] == current) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + } else { + ADVANCE(CHECK_REGISTER_EQ_POS); + } + DISPATCH(); + } + BYTECODE(CHECK_NOT_REGS_EQUAL) { + if (registers[LoadPacked24Unsigned(insn)] == + registers[Load32Aligned(pc + 4)]) { + ADVANCE(CHECK_NOT_REGS_EQUAL); + } else { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 8)); + } + DISPATCH(); + } + BYTECODE(CHECK_NOT_BACK_REF) { + int from = registers[LoadPacked24Unsigned(insn)]; + int len = registers[LoadPacked24Unsigned(insn) + 1] - from; + if (from >= 0 && len > 0) { + if (current + len > subject.length() || + !CompareCharsEqual(&subject[from], &subject[current], len)) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + DISPATCH(); + } + ADVANCE_CURRENT_POSITION(len); + } + ADVANCE(CHECK_NOT_BACK_REF); + DISPATCH(); + } + BYTECODE(CHECK_NOT_BACK_REF_BACKWARD) { + int from = registers[LoadPacked24Unsigned(insn)]; + int len = registers[LoadPacked24Unsigned(insn) + 1] - from; + if (from >= 0 && len > 0) { + if (current - len < 0 || + !CompareCharsEqual(&subject[from], &subject[current - len], len)) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + DISPATCH(); + } + SET_CURRENT_POSITION(current - len); + } + ADVANCE(CHECK_NOT_BACK_REF_BACKWARD); + DISPATCH(); + } + BYTECODE(CHECK_NOT_BACK_REF_NO_CASE_UNICODE) { + int from = registers[LoadPacked24Unsigned(insn)]; + int len = registers[LoadPacked24Unsigned(insn) + 1] - from; + if (from >= 0 && len > 0) { + if (current + len > subject.length() || + !BackRefMatchesNoCase(isolate, from, current, len, subject, true)) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + DISPATCH(); + } + ADVANCE_CURRENT_POSITION(len); + } + ADVANCE(CHECK_NOT_BACK_REF_NO_CASE_UNICODE); + DISPATCH(); + } + BYTECODE(CHECK_NOT_BACK_REF_NO_CASE) { + int from = registers[LoadPacked24Unsigned(insn)]; + int len = registers[LoadPacked24Unsigned(insn) + 1] - from; + if (from >= 0 && len > 0) { + if (current + len > subject.length() || + !BackRefMatchesNoCase(isolate, from, current, len, subject, + false)) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + DISPATCH(); + } + ADVANCE_CURRENT_POSITION(len); + } + ADVANCE(CHECK_NOT_BACK_REF_NO_CASE); + DISPATCH(); + } + BYTECODE(CHECK_NOT_BACK_REF_NO_CASE_UNICODE_BACKWARD) { + int from = registers[LoadPacked24Unsigned(insn)]; + int len = registers[LoadPacked24Unsigned(insn) + 1] - from; + if (from >= 0 && len > 0) { + if (current - len < 0 || + !BackRefMatchesNoCase(isolate, from, current - len, len, subject, + true)) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + DISPATCH(); + } + SET_CURRENT_POSITION(current - len); + } + ADVANCE(CHECK_NOT_BACK_REF_NO_CASE_UNICODE_BACKWARD); + DISPATCH(); + } + BYTECODE(CHECK_NOT_BACK_REF_NO_CASE_BACKWARD) { + int from = registers[LoadPacked24Unsigned(insn)]; + int len = registers[LoadPacked24Unsigned(insn) + 1] - from; + if (from >= 0 && len > 0) { + if (current - len < 0 || + !BackRefMatchesNoCase(isolate, from, current - len, len, subject, + false)) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + DISPATCH(); + } + SET_CURRENT_POSITION(current - len); + } + ADVANCE(CHECK_NOT_BACK_REF_NO_CASE_BACKWARD); + DISPATCH(); + } + BYTECODE(CHECK_AT_START) { + if (current + LoadPacked24Signed(insn) == 0) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + } else { + ADVANCE(CHECK_AT_START); + } + DISPATCH(); + } + BYTECODE(CHECK_NOT_AT_START) { + if (current + LoadPacked24Signed(insn) == 0) { + ADVANCE(CHECK_NOT_AT_START); + } else { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + } + DISPATCH(); + } + BYTECODE(SET_CURRENT_POSITION_FROM_END) { + ADVANCE(SET_CURRENT_POSITION_FROM_END); + int by = LoadPacked24Unsigned(insn); + if (subject.length() - current > by) { + SET_CURRENT_POSITION(subject.length() - by); + current_char = subject[current - 1]; + } + DISPATCH(); + } + BYTECODE(CHECK_CURRENT_POSITION) { + int pos = current + LoadPacked24Signed(insn); + if (pos > subject.length() || pos < 0) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 4)); + } else { + ADVANCE(CHECK_CURRENT_POSITION); + } + DISPATCH(); + } + BYTECODE(SKIP_UNTIL_CHAR) { + int32_t load_offset = LoadPacked24Signed(insn); + int32_t advance = Load16AlignedSigned(pc + 4); + uint32_t c = Load16Aligned(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(); + } + ADVANCE_CURRENT_POSITION(advance); + } + SET_PC_FROM_OFFSET(Load32Aligned(pc + 12)); + DISPATCH(); + } + BYTECODE(SKIP_UNTIL_CHAR_AND) { + int32_t load_offset = LoadPacked24Signed(insn); + int32_t advance = Load16AlignedSigned(pc + 4); + uint16_t c = Load16Aligned(pc + 6); + uint32_t mask = Load32Aligned(pc + 8); + int32_t maximum_offset = Load32Aligned(pc + 12); + while (static_cast<uintptr_t>(current + maximum_offset) <= + static_cast<uintptr_t>(subject.length())) { + current_char = subject[current + load_offset]; + if (c == (current_char & mask)) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 16)); + DISPATCH(); + } + ADVANCE_CURRENT_POSITION(advance); + } + SET_PC_FROM_OFFSET(Load32Aligned(pc + 20)); + 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); + int32_t maximum_offset = Load32Aligned(pc + 8); + while (static_cast<uintptr_t>(current + maximum_offset) <= + static_cast<uintptr_t>(subject.length())) { + current_char = subject[current + load_offset]; + if (c == current_char) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 12)); + DISPATCH(); + } + ADVANCE_CURRENT_POSITION(advance); + } + SET_PC_FROM_OFFSET(Load32Aligned(pc + 16)); + DISPATCH(); + } + BYTECODE(SKIP_UNTIL_BIT_IN_TABLE) { + int32_t load_offset = LoadPacked24Signed(insn); + int32_t advance = Load16AlignedSigned(pc + 4); + const uint8_t* table = pc + 8; + while (IndexIsInBounds(current + load_offset, subject.length())) { + current_char = subject[current + load_offset]; + if (CheckBitInTable(current_char, table)) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 24)); + DISPATCH(); + } + ADVANCE_CURRENT_POSITION(advance); + } + SET_PC_FROM_OFFSET(Load32Aligned(pc + 28)); + 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); + 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)); + DISPATCH(); + } + if (!CheckBitInTable(current_char, table)) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 24)); + DISPATCH(); + } + ADVANCE_CURRENT_POSITION(advance); + } + SET_PC_FROM_OFFSET(Load32Aligned(pc + 28)); + 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); + 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. + if (c == current_char) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 12)); + DISPATCH(); + } + if (c2 == current_char) { + SET_PC_FROM_OFFSET(Load32Aligned(pc + 12)); + DISPATCH(); + } + ADVANCE_CURRENT_POSITION(advance); + } + SET_PC_FROM_OFFSET(Load32Aligned(pc + 16)); + DISPATCH(); + } +#if V8_USE_COMPUTED_GOTO +// Lint gets confused a lot if we just use !V8_USE_COMPUTED_GOTO or ifndef +// V8_USE_COMPUTED_GOTO here. +#else + default: + UNREACHABLE(); + } + // Label we jump to in DISPATCH(). There must be no instructions between the + // end of the switch, this label and the end of the loop. + switch_dispatch_continuation : {} +#endif // V8_USE_COMPUTED_GOTO + } +} + +#undef BYTECODE +#undef ADVANCE_CURRENT_POSITION +#undef SET_CURRENT_POSITION +#undef DISPATCH +#undef DECODE +#undef SET_PC_FROM_OFFSET +#undef ADVANCE +#undef BC_LABEL +#undef V8_USE_COMPUTED_GOTO + +} // namespace + +// static +IrregexpInterpreter::Result IrregexpInterpreter::Match( + Isolate* isolate, JSRegExp regexp, String subject_string, + int* output_registers, int output_register_count, int start_position, + RegExp::CallOrigin call_origin) { + 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(); + + return MatchInternal(isolate, code_array, subject_string, output_registers, + output_register_count, total_register_count, + 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()); + + // TODO(chromium:1262676): Remove this CHECK once fixed. + CHECK(code_array.IsByteArray()); + + // 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); + // 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()) { + base::Vector<const uint8_t> subject_vector = + subject_content.ToOneByteVector(); + if (start_position != 0) previous_char = subject_vector[start_position - 1]; + return RawMatch(isolate, code_array, subject_string, subject_vector, + output_registers, output_register_count, + total_register_count, start_position, previous_char, + call_origin, backtrack_limit); + } else { + DCHECK(subject_content.IsTwoByte()); + base::Vector<const base::uc16> subject_vector = + subject_content.ToUC16Vector(); + if (start_position != 0) previous_char = subject_vector[start_position - 1]; + return RawMatch(isolate, code_array, subject_string, subject_vector, + output_registers, output_register_count, + total_register_count, start_position, previous_char, + call_origin, backtrack_limit); + } +} + +#ifndef COMPILING_IRREGEXP_FOR_EXTERNAL_EMBEDDER + +// This method is called through an external reference from RegExpExecInternal +// builtin. +IrregexpInterpreter::Result IrregexpInterpreter::MatchForCallFromJs( + Address subject, int32_t start_position, Address, Address, + int* output_registers, int32_t output_register_count, + RegExp::CallOrigin call_origin, Isolate* isolate, Address regexp) { + DCHECK_NOT_NULL(isolate); + DCHECK_NOT_NULL(output_registers); + DCHECK(call_origin == RegExp::CallOrigin::kFromJs); + + 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)); + + if (regexp_obj.MarkedForTierUp()) { + // Returning RETRY will re-enter through runtime, where actual recompilation + // for tier-up takes place. + return IrregexpInterpreter::RETRY; + } + + return Match(isolate, regexp_obj, subject_string, output_registers, + output_register_count, start_position, call_origin); +} + +#endif // !COMPILING_IRREGEXP_FOR_EXTERNAL_EMBEDDER + +IrregexpInterpreter::Result IrregexpInterpreter::MatchForCallFromRuntime( + Isolate* isolate, Handle<JSRegExp> regexp, Handle<String> subject_string, + int* output_registers, int output_register_count, int start_position) { + return Match(isolate, *regexp, *subject_string, output_registers, + output_register_count, start_position, + RegExp::CallOrigin::kFromRuntime); +} + +} // namespace internal +} // namespace v8 diff --git a/js/src/irregexp/imported/regexp-interpreter.h b/js/src/irregexp/imported/regexp-interpreter.h new file mode 100644 index 0000000000..bc55be2b8c --- /dev/null +++ b/js/src/irregexp/imported/regexp-interpreter.h @@ -0,0 +1,68 @@ +// Copyright 2011 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. + +// A simple interpreter for the Irregexp byte code. + +#ifndef V8_REGEXP_REGEXP_INTERPRETER_H_ +#define V8_REGEXP_REGEXP_INTERPRETER_H_ + +#include "irregexp/imported/regexp.h" + +namespace v8 { +namespace internal { + +class ByteArray; + +class V8_EXPORT_PRIVATE IrregexpInterpreter : public AllStatic { + public: + enum Result { + FAILURE = RegExp::kInternalRegExpFailure, + SUCCESS = RegExp::kInternalRegExpSuccess, + EXCEPTION = RegExp::kInternalRegExpException, + RETRY = RegExp::kInternalRegExpRetry, + FALLBACK_TO_EXPERIMENTAL = RegExp::kInternalRegExpFallbackToExperimental, + }; + + // In case a StackOverflow occurs, a StackOverflowException is created and + // EXCEPTION is returned. + static Result MatchForCallFromRuntime( + Isolate* isolate, Handle<JSRegExp> regexp, Handle<String> subject_string, + int* output_registers, int output_register_count, int start_position); + + // In case a StackOverflow occurs, EXCEPTION is returned. The caller is + // responsible for creating the exception. + // + // RETRY is returned if a retry through the runtime is needed (e.g. when + // interrupts have been scheduled or the regexp is marked for tier-up). + // + // Arguments input_start and input_end are unused. They are only passed to + // match the signature of the native irregex code. + // + // Arguments output_registers and output_register_count describe the results + // array, which will contain register values of all captures if SUCCESS is + // returned. For all other return codes, the results array remains unmodified. + static Result MatchForCallFromJs(Address subject, int32_t start_position, + Address input_start, Address input_end, + 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, + 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); +}; + +} // namespace internal +} // namespace v8 + +#endif // V8_REGEXP_REGEXP_INTERPRETER_H_ diff --git a/js/src/irregexp/imported/regexp-macro-assembler-arch.h b/js/src/irregexp/imported/regexp-macro-assembler-arch.h new file mode 100644 index 0000000000..a755e7c1b3 --- /dev/null +++ b/js/src/irregexp/imported/regexp-macro-assembler-arch.h @@ -0,0 +1,7 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: set ts=8 sts=2 et sw=2 tw=80: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "irregexp/RegExpNativeMacroAssembler.h" diff --git a/js/src/irregexp/imported/regexp-macro-assembler-tracer.cc b/js/src/irregexp/imported/regexp-macro-assembler-tracer.cc new file mode 100644 index 0000000000..6444ca3c60 --- /dev/null +++ b/js/src/irregexp/imported/regexp-macro-assembler-tracer.cc @@ -0,0 +1,438 @@ +// Copyright 2012 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-macro-assembler-tracer.h" + + +namespace v8 { +namespace internal { + +RegExpMacroAssemblerTracer::RegExpMacroAssemblerTracer( + Isolate* isolate, RegExpMacroAssembler* assembler) + : RegExpMacroAssembler(isolate, assembler->zone()), assembler_(assembler) { + PrintF("RegExpMacroAssembler%s();\n", + ImplementationToString(assembler->Implementation())); +} + +RegExpMacroAssemblerTracer::~RegExpMacroAssemblerTracer() = default; + +void RegExpMacroAssemblerTracer::AbortedCodeGeneration() { + PrintF(" AbortedCodeGeneration\n"); + assembler_->AbortedCodeGeneration(); +} + + +// This is used for printing out debugging information. It makes an integer +// that is closely related to the address of an object. +static int LabelToInt(Label* label) { + return static_cast<int>(reinterpret_cast<intptr_t>(label)); +} + + +void RegExpMacroAssemblerTracer::Bind(Label* label) { + PrintF("label[%08x]: (Bind)\n", LabelToInt(label)); + assembler_->Bind(label); +} + + +void RegExpMacroAssemblerTracer::AdvanceCurrentPosition(int by) { + PrintF(" AdvanceCurrentPosition(by=%d);\n", by); + assembler_->AdvanceCurrentPosition(by); +} + + +void RegExpMacroAssemblerTracer::CheckGreedyLoop(Label* label) { + PrintF(" CheckGreedyLoop(label[%08x]);\n\n", LabelToInt(label)); + assembler_->CheckGreedyLoop(label); +} + + +void RegExpMacroAssemblerTracer::PopCurrentPosition() { + PrintF(" PopCurrentPosition();\n"); + assembler_->PopCurrentPosition(); +} + + +void RegExpMacroAssemblerTracer::PushCurrentPosition() { + PrintF(" PushCurrentPosition();\n"); + assembler_->PushCurrentPosition(); +} + + +void RegExpMacroAssemblerTracer::Backtrack() { + PrintF(" Backtrack();\n"); + assembler_->Backtrack(); +} + + +void RegExpMacroAssemblerTracer::GoTo(Label* label) { + PrintF(" GoTo(label[%08x]);\n\n", LabelToInt(label)); + assembler_->GoTo(label); +} + + +void RegExpMacroAssemblerTracer::PushBacktrack(Label* label) { + PrintF(" PushBacktrack(label[%08x]);\n", LabelToInt(label)); + assembler_->PushBacktrack(label); +} + + +bool RegExpMacroAssemblerTracer::Succeed() { + bool restart = assembler_->Succeed(); + PrintF(" Succeed();%s\n", restart ? " [restart for global match]" : ""); + return restart; +} + + +void RegExpMacroAssemblerTracer::Fail() { + PrintF(" Fail();"); + assembler_->Fail(); +} + + +void RegExpMacroAssemblerTracer::PopRegister(int register_index) { + PrintF(" PopRegister(register=%d);\n", register_index); + assembler_->PopRegister(register_index); +} + + +void RegExpMacroAssemblerTracer::PushRegister( + int register_index, + StackCheckFlag check_stack_limit) { + PrintF(" PushRegister(register=%d, %s);\n", + register_index, + check_stack_limit ? "check stack limit" : ""); + assembler_->PushRegister(register_index, check_stack_limit); +} + + +void RegExpMacroAssemblerTracer::AdvanceRegister(int reg, int by) { + PrintF(" AdvanceRegister(register=%d, by=%d);\n", reg, by); + assembler_->AdvanceRegister(reg, by); +} + + +void RegExpMacroAssemblerTracer::SetCurrentPositionFromEnd(int by) { + PrintF(" SetCurrentPositionFromEnd(by=%d);\n", by); + assembler_->SetCurrentPositionFromEnd(by); +} + + +void RegExpMacroAssemblerTracer::SetRegister(int register_index, int to) { + PrintF(" SetRegister(register=%d, to=%d);\n", register_index, to); + assembler_->SetRegister(register_index, to); +} + + +void RegExpMacroAssemblerTracer::WriteCurrentPositionToRegister(int reg, + int cp_offset) { + PrintF(" WriteCurrentPositionToRegister(register=%d,cp_offset=%d);\n", + reg, + cp_offset); + assembler_->WriteCurrentPositionToRegister(reg, cp_offset); +} + + +void RegExpMacroAssemblerTracer::ClearRegisters(int reg_from, int reg_to) { + PrintF(" ClearRegister(from=%d, to=%d);\n", reg_from, reg_to); + assembler_->ClearRegisters(reg_from, reg_to); +} + + +void RegExpMacroAssemblerTracer::ReadCurrentPositionFromRegister(int reg) { + PrintF(" ReadCurrentPositionFromRegister(register=%d);\n", reg); + assembler_->ReadCurrentPositionFromRegister(reg); +} + + +void RegExpMacroAssemblerTracer::WriteStackPointerToRegister(int reg) { + PrintF(" WriteStackPointerToRegister(register=%d);\n", reg); + assembler_->WriteStackPointerToRegister(reg); +} + + +void RegExpMacroAssemblerTracer::ReadStackPointerFromRegister(int reg) { + PrintF(" ReadStackPointerFromRegister(register=%d);\n", reg); + assembler_->ReadStackPointerFromRegister(reg); +} + +void RegExpMacroAssemblerTracer::LoadCurrentCharacterImpl( + int cp_offset, Label* on_end_of_input, bool check_bounds, int characters, + int eats_at_least) { + const char* check_msg = check_bounds ? "" : " (unchecked)"; + PrintF( + " LoadCurrentCharacter(cp_offset=%d, label[%08x]%s (%d chars) (eats at " + "least %d));\n", + cp_offset, LabelToInt(on_end_of_input), check_msg, characters, + eats_at_least); + assembler_->LoadCurrentCharacter(cp_offset, on_end_of_input, check_bounds, + characters, eats_at_least); +} + +namespace { + +class PrintablePrinter { + public: + explicit PrintablePrinter(base::uc16 character) : character_(character) {} + + const char* operator*() { + if (character_ >= ' ' && character_ <= '~') { + buffer_[0] = '('; + buffer_[1] = static_cast<char>(character_); + buffer_[2] = ')'; + buffer_[3] = '\0'; + } else { + buffer_[0] = '\0'; + } + return &buffer_[0]; + } + + private: + base::uc16 character_; + char buffer_[4]; +}; + +} // namespace + +void RegExpMacroAssemblerTracer::CheckCharacterLT(base::uc16 limit, + Label* on_less) { + PrintablePrinter printable(limit); + PrintF(" CheckCharacterLT(c=0x%04x%s, label[%08x]);\n", + limit, + *printable, + LabelToInt(on_less)); + assembler_->CheckCharacterLT(limit, on_less); +} + +void RegExpMacroAssemblerTracer::CheckCharacterGT(base::uc16 limit, + Label* on_greater) { + PrintablePrinter printable(limit); + PrintF(" CheckCharacterGT(c=0x%04x%s, label[%08x]);\n", + limit, + *printable, + LabelToInt(on_greater)); + assembler_->CheckCharacterGT(limit, on_greater); +} + +void RegExpMacroAssemblerTracer::CheckCharacter(unsigned c, Label* on_equal) { + PrintablePrinter printable(c); + PrintF(" CheckCharacter(c=0x%04x%s, label[%08x]);\n", + c, + *printable, + LabelToInt(on_equal)); + assembler_->CheckCharacter(c, on_equal); +} + +void RegExpMacroAssemblerTracer::CheckAtStart(int cp_offset, + Label* on_at_start) { + PrintF(" CheckAtStart(cp_offset=%d, label[%08x]);\n", cp_offset, + LabelToInt(on_at_start)); + assembler_->CheckAtStart(cp_offset, on_at_start); +} + +void RegExpMacroAssemblerTracer::CheckNotAtStart(int cp_offset, + Label* on_not_at_start) { + PrintF(" CheckNotAtStart(cp_offset=%d, label[%08x]);\n", cp_offset, + LabelToInt(on_not_at_start)); + assembler_->CheckNotAtStart(cp_offset, on_not_at_start); +} + + +void RegExpMacroAssemblerTracer::CheckNotCharacter(unsigned c, + Label* on_not_equal) { + PrintablePrinter printable(c); + PrintF(" CheckNotCharacter(c=0x%04x%s, label[%08x]);\n", + c, + *printable, + LabelToInt(on_not_equal)); + assembler_->CheckNotCharacter(c, on_not_equal); +} + + +void RegExpMacroAssemblerTracer::CheckCharacterAfterAnd( + unsigned c, + unsigned mask, + Label* on_equal) { + PrintablePrinter printable(c); + PrintF(" CheckCharacterAfterAnd(c=0x%04x%s, mask=0x%04x, label[%08x]);\n", + c, + *printable, + mask, + LabelToInt(on_equal)); + assembler_->CheckCharacterAfterAnd(c, mask, on_equal); +} + + +void RegExpMacroAssemblerTracer::CheckNotCharacterAfterAnd( + unsigned c, + unsigned mask, + Label* on_not_equal) { + PrintablePrinter printable(c); + PrintF(" CheckNotCharacterAfterAnd(c=0x%04x%s, mask=0x%04x, label[%08x]);\n", + c, + *printable, + mask, + LabelToInt(on_not_equal)); + assembler_->CheckNotCharacterAfterAnd(c, mask, on_not_equal); +} + +void RegExpMacroAssemblerTracer::CheckNotCharacterAfterMinusAnd( + base::uc16 c, base::uc16 minus, base::uc16 mask, Label* on_not_equal) { + PrintF(" CheckNotCharacterAfterMinusAnd(c=0x%04x, minus=%04x, mask=0x%04x, " + "label[%08x]);\n", + c, + minus, + mask, + LabelToInt(on_not_equal)); + assembler_->CheckNotCharacterAfterMinusAnd(c, minus, mask, on_not_equal); +} + +void RegExpMacroAssemblerTracer::CheckCharacterInRange(base::uc16 from, + base::uc16 to, + Label* on_not_in_range) { + PrintablePrinter printable_from(from); + PrintablePrinter printable_to(to); + PrintF(" CheckCharacterInRange(from=0x%04x%s, to=0x%04x%s, label[%08x]);\n", + from, + *printable_from, + to, + *printable_to, + LabelToInt(on_not_in_range)); + assembler_->CheckCharacterInRange(from, to, on_not_in_range); +} + +void RegExpMacroAssemblerTracer::CheckCharacterNotInRange(base::uc16 from, + base::uc16 to, + Label* on_in_range) { + PrintablePrinter printable_from(from); + PrintablePrinter printable_to(to); + PrintF( + " CheckCharacterNotInRange(from=0x%04x%s," " to=%04x%s, label[%08x]);\n", + from, + *printable_from, + to, + *printable_to, + LabelToInt(on_in_range)); + assembler_->CheckCharacterNotInRange(from, to, on_in_range); +} + +namespace { + +void PrintRangeArray(const ZoneList<CharacterRange>* ranges) { + for (int i = 0; i < ranges->length(); i++) { + base::uc16 from = ranges->at(i).from(); + base::uc16 to = ranges->at(i).to(); + PrintablePrinter printable_from(from); + PrintablePrinter printable_to(to); + PrintF(" [from=0x%04x%s, to=%04x%s],\n", from, *printable_from, to, + *printable_to); + } +} + +} // namespace + +bool RegExpMacroAssemblerTracer::CheckCharacterInRangeArray( + const ZoneList<CharacterRange>* ranges, Label* on_in_range) { + PrintF( + " CheckCharacterInRangeArray(\n" + " label[%08x]);\n", + LabelToInt(on_in_range)); + PrintRangeArray(ranges); + return assembler_->CheckCharacterInRangeArray(ranges, on_in_range); +} + +bool RegExpMacroAssemblerTracer::CheckCharacterNotInRangeArray( + const ZoneList<CharacterRange>* ranges, Label* on_not_in_range) { + PrintF( + " CheckCharacterNotInRangeArray(\n" + " label[%08x]);\n", + LabelToInt(on_not_in_range)); + PrintRangeArray(ranges); + return assembler_->CheckCharacterNotInRangeArray(ranges, on_not_in_range); +} + +void RegExpMacroAssemblerTracer::CheckBitInTable( + Handle<ByteArray> table, Label* on_bit_set) { + PrintF(" CheckBitInTable(label[%08x] ", LabelToInt(on_bit_set)); + for (int i = 0; i < kTableSize; i++) { + PrintF("%c", table->get(i) != 0 ? 'X' : '.'); + if (i % 32 == 31 && i != kTableMask) { + PrintF("\n "); + } + } + PrintF(");\n"); + assembler_->CheckBitInTable(table, on_bit_set); +} + + +void RegExpMacroAssemblerTracer::CheckNotBackReference(int start_reg, + bool read_backward, + Label* on_no_match) { + PrintF(" CheckNotBackReference(register=%d, %s, label[%08x]);\n", start_reg, + read_backward ? "backward" : "forward", LabelToInt(on_no_match)); + assembler_->CheckNotBackReference(start_reg, read_backward, on_no_match); +} + +void RegExpMacroAssemblerTracer::CheckNotBackReferenceIgnoreCase( + int start_reg, bool read_backward, bool unicode, Label* on_no_match) { + PrintF(" CheckNotBackReferenceIgnoreCase(register=%d, %s %s, label[%08x]);\n", + start_reg, read_backward ? "backward" : "forward", + unicode ? "unicode" : "non-unicode", LabelToInt(on_no_match)); + assembler_->CheckNotBackReferenceIgnoreCase(start_reg, read_backward, unicode, + on_no_match); +} + +void RegExpMacroAssemblerTracer::CheckPosition(int cp_offset, + Label* on_outside_input) { + PrintF(" CheckPosition(cp_offset=%d, label[%08x]);\n", cp_offset, + LabelToInt(on_outside_input)); + assembler_->CheckPosition(cp_offset, on_outside_input); +} + +bool RegExpMacroAssemblerTracer::CheckSpecialClassRanges( + StandardCharacterSet type, Label* on_no_match) { + bool supported = assembler_->CheckSpecialClassRanges(type, on_no_match); + PrintF(" CheckSpecialClassRanges(type='%c', label[%08x]): %s;\n", + static_cast<char>(type), LabelToInt(on_no_match), + supported ? "true" : "false"); + return supported; +} + +void RegExpMacroAssemblerTracer::IfRegisterLT(int register_index, + int comparand, Label* if_lt) { + PrintF(" IfRegisterLT(register=%d, number=%d, label[%08x]);\n", + register_index, comparand, LabelToInt(if_lt)); + assembler_->IfRegisterLT(register_index, comparand, if_lt); +} + + +void RegExpMacroAssemblerTracer::IfRegisterEqPos(int register_index, + Label* if_eq) { + PrintF(" IfRegisterEqPos(register=%d, label[%08x]);\n", + register_index, LabelToInt(if_eq)); + assembler_->IfRegisterEqPos(register_index, if_eq); +} + + +void RegExpMacroAssemblerTracer::IfRegisterGE(int register_index, + int comparand, Label* if_ge) { + PrintF(" IfRegisterGE(register=%d, number=%d, label[%08x]);\n", + register_index, comparand, LabelToInt(if_ge)); + assembler_->IfRegisterGE(register_index, comparand, if_ge); +} + + +RegExpMacroAssembler::IrregexpImplementation + RegExpMacroAssemblerTracer::Implementation() { + return assembler_->Implementation(); +} + + +Handle<HeapObject> RegExpMacroAssemblerTracer::GetCode(Handle<String> source) { + PrintF(" GetCode(%s);\n", source->ToCString().get()); + return assembler_->GetCode(source); +} + +} // namespace internal +} // namespace v8 diff --git a/js/src/irregexp/imported/regexp-macro-assembler-tracer.h b/js/src/irregexp/imported/regexp-macro-assembler-tracer.h new file mode 100644 index 0000000000..3fadf1a893 --- /dev/null +++ b/js/src/irregexp/imported/regexp-macro-assembler-tracer.h @@ -0,0 +1,90 @@ +// Copyright 2008 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. + +#ifndef V8_REGEXP_REGEXP_MACRO_ASSEMBLER_TRACER_H_ +#define V8_REGEXP_REGEXP_MACRO_ASSEMBLER_TRACER_H_ + +#include "irregexp/imported/regexp-macro-assembler.h" + +namespace v8 { +namespace internal { + +// Decorator on a RegExpMacroAssembler that write all calls. +class RegExpMacroAssemblerTracer: public RegExpMacroAssembler { + public: + RegExpMacroAssemblerTracer(Isolate* isolate, RegExpMacroAssembler* assembler); + ~RegExpMacroAssemblerTracer() override; + void AbortedCodeGeneration() override; + int stack_limit_slack() override { return assembler_->stack_limit_slack(); } + bool CanReadUnaligned() const override { + return assembler_->CanReadUnaligned(); + } + void AdvanceCurrentPosition(int by) override; // Signed cp change. + void AdvanceRegister(int reg, int by) override; // r[reg] += by. + void Backtrack() override; + void Bind(Label* label) override; + void CheckCharacter(unsigned c, Label* on_equal) override; + void CheckCharacterAfterAnd(unsigned c, unsigned and_with, + Label* on_equal) override; + void CheckCharacterGT(base::uc16 limit, Label* on_greater) override; + void CheckCharacterLT(base::uc16 limit, Label* on_less) override; + void CheckGreedyLoop(Label* on_tos_equals_current_position) override; + void CheckAtStart(int cp_offset, Label* on_at_start) override; + void CheckNotAtStart(int cp_offset, Label* on_not_at_start) override; + void CheckNotBackReference(int start_reg, bool read_backward, + Label* on_no_match) override; + void CheckNotBackReferenceIgnoreCase(int start_reg, bool read_backward, + bool unicode, + Label* on_no_match) override; + void CheckNotCharacter(unsigned c, Label* on_not_equal) override; + void CheckNotCharacterAfterAnd(unsigned c, unsigned and_with, + Label* on_not_equal) override; + void CheckNotCharacterAfterMinusAnd(base::uc16 c, base::uc16 minus, + base::uc16 and_with, + Label* on_not_equal) override; + void CheckCharacterInRange(base::uc16 from, base::uc16 to, + Label* on_in_range) override; + void CheckCharacterNotInRange(base::uc16 from, base::uc16 to, + Label* on_not_in_range) override; + bool CheckCharacterInRangeArray(const ZoneList<CharacterRange>* ranges, + Label* on_in_range) override; + bool CheckCharacterNotInRangeArray(const ZoneList<CharacterRange>* ranges, + Label* on_not_in_range) override; + void CheckBitInTable(Handle<ByteArray> table, Label* on_bit_set) override; + void CheckPosition(int cp_offset, Label* on_outside_input) override; + bool CheckSpecialClassRanges(StandardCharacterSet type, + Label* on_no_match) override; + void Fail() override; + Handle<HeapObject> GetCode(Handle<String> source) override; + void GoTo(Label* label) override; + void IfRegisterGE(int reg, int comparand, Label* if_ge) override; + void IfRegisterLT(int reg, int comparand, Label* if_lt) override; + void IfRegisterEqPos(int reg, Label* if_eq) override; + IrregexpImplementation Implementation() override; + void LoadCurrentCharacterImpl(int cp_offset, Label* on_end_of_input, + bool check_bounds, int characters, + int eats_at_least) override; + void PopCurrentPosition() override; + void PopRegister(int register_index) override; + void PushBacktrack(Label* label) override; + void PushCurrentPosition() override; + void PushRegister(int register_index, + StackCheckFlag check_stack_limit) override; + void ReadCurrentPositionFromRegister(int reg) override; + void ReadStackPointerFromRegister(int reg) override; + void SetCurrentPositionFromEnd(int by) override; + void SetRegister(int register_index, int to) override; + bool Succeed() override; + void WriteCurrentPositionToRegister(int reg, int cp_offset) override; + void ClearRegisters(int reg_from, int reg_to) override; + void WriteStackPointerToRegister(int reg) override; + + private: + RegExpMacroAssembler* assembler_; +}; + +} // namespace internal +} // namespace v8 + +#endif // V8_REGEXP_REGEXP_MACRO_ASSEMBLER_TRACER_H_ diff --git a/js/src/irregexp/imported/regexp-macro-assembler.cc b/js/src/irregexp/imported/regexp-macro-assembler.cc new file mode 100644 index 0000000000..b4d99bf775 --- /dev/null +++ b/js/src/irregexp/imported/regexp-macro-assembler.cc @@ -0,0 +1,520 @@ +// Copyright 2012 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-macro-assembler.h" + +#include "irregexp/imported/regexp-stack.h" +#include "irregexp/imported/special-case.h" + +#ifdef V8_INTL_SUPPORT +#include "unicode/uchar.h" +#include "unicode/unistr.h" +#endif // V8_INTL_SUPPORT + +namespace v8 { +namespace internal { + +RegExpMacroAssembler::RegExpMacroAssembler(Isolate* isolate, Zone* zone) + : slow_safe_compiler_(false), + backtrack_limit_(JSRegExp::kNoBacktrackLimit), + global_mode_(NOT_GLOBAL), + isolate_(isolate), + zone_(zone) {} + +bool RegExpMacroAssembler::has_backtrack_limit() const { + return backtrack_limit_ != JSRegExp::kNoBacktrackLimit; +} + +// static +int RegExpMacroAssembler::CaseInsensitiveCompareNonUnicode(Address byte_offset1, + Address byte_offset2, + size_t byte_length, + Isolate* isolate) { +#ifdef V8_INTL_SUPPORT + // This function is not allowed to cause a garbage collection. + // A GC might move the calling generated code and invalidate the + // return address on the stack. + DisallowGarbageCollection no_gc; + DCHECK_EQ(0, byte_length % 2); + size_t length = byte_length / 2; + base::uc16* substring1 = reinterpret_cast<base::uc16*>(byte_offset1); + base::uc16* substring2 = reinterpret_cast<base::uc16*>(byte_offset2); + + for (size_t i = 0; i < length; i++) { + UChar32 c1 = RegExpCaseFolding::Canonicalize(substring1[i]); + UChar32 c2 = RegExpCaseFolding::Canonicalize(substring2[i]); + if (c1 != c2) { + return 0; + } + } + return 1; +#else + return CaseInsensitiveCompareUnicode(byte_offset1, byte_offset2, byte_length, + isolate); +#endif +} + +// static +int RegExpMacroAssembler::CaseInsensitiveCompareUnicode(Address byte_offset1, + Address byte_offset2, + size_t byte_length, + Isolate* isolate) { + // This function is not allowed to cause a garbage collection. + // A GC might move the calling generated code and invalidate the + // return address on the stack. + DisallowGarbageCollection no_gc; + DCHECK_EQ(0, byte_length % 2); + +#ifdef V8_INTL_SUPPORT + int32_t length = static_cast<int32_t>(byte_length >> 1); + icu::UnicodeString uni_str_1(reinterpret_cast<const char16_t*>(byte_offset1), + length); + return uni_str_1.caseCompare(reinterpret_cast<const char16_t*>(byte_offset2), + length, U_FOLD_CASE_DEFAULT) == 0; +#else + base::uc16* substring1 = reinterpret_cast<base::uc16*>(byte_offset1); + base::uc16* substring2 = reinterpret_cast<base::uc16*>(byte_offset2); + size_t length = byte_length >> 1; + DCHECK_NOT_NULL(isolate); + unibrow::Mapping<unibrow::Ecma262Canonicalize>* canonicalize = + isolate->regexp_macro_assembler_canonicalize(); + for (size_t i = 0; i < length; i++) { + unibrow::uchar c1 = substring1[i]; + unibrow::uchar c2 = substring2[i]; + if (c1 != c2) { + unibrow::uchar s1[1] = {c1}; + canonicalize->get(c1, '\0', s1); + if (s1[0] != c2) { + unibrow::uchar s2[1] = {c2}; + canonicalize->get(c2, '\0', s2); + if (s1[0] != s2[0]) { + return 0; + } + } + } + } + return 1; +#endif // V8_INTL_SUPPORT +} + +namespace { + +uint32_t Hash(const ZoneList<CharacterRange>* ranges) { + size_t seed = 0; + for (int i = 0; i < ranges->length(); i++) { + const CharacterRange& r = ranges->at(i); + seed = base::hash_combine(seed, r.from(), r.to()); + } + return static_cast<uint32_t>(seed); +} + +constexpr base::uc32 MaskEndOfRangeMarker(base::uc32 c) { + // CharacterRanges may use 0x10ffff as the end-of-range marker irrespective + // of whether the regexp IsUnicode or not; translate the marker value here. + DCHECK_IMPLIES(c > kMaxUInt16, c == String::kMaxCodePoint); + return c & 0xffff; +} + +int RangeArrayLengthFor(const ZoneList<CharacterRange>* ranges) { + const int ranges_length = ranges->length(); + return MaskEndOfRangeMarker(ranges->at(ranges_length - 1).to()) == kMaxUInt16 + ? ranges_length * 2 - 1 + : ranges_length * 2; +} + +bool Equals(const ZoneList<CharacterRange>* lhs, + const Handle<FixedUInt16Array>& rhs) { + const int rhs_length = rhs->length(); + if (rhs_length != RangeArrayLengthFor(lhs)) return false; + for (int i = 0; i < lhs->length(); i++) { + const CharacterRange& r = lhs->at(i); + if (rhs->get(i * 2 + 0) != r.from()) return false; + if (i * 2 + 1 == rhs_length) break; + if (rhs->get(i * 2 + 1) != r.to() + 1) return false; + } + return true; +} + +Handle<FixedUInt16Array> MakeRangeArray( + Isolate* isolate, const ZoneList<CharacterRange>* ranges) { + const int ranges_length = ranges->length(); + const int range_array_length = RangeArrayLengthFor(ranges); + Handle<FixedUInt16Array> range_array = + FixedUInt16Array::New(isolate, range_array_length); + for (int i = 0; i < ranges_length; i++) { + const CharacterRange& r = ranges->at(i); + DCHECK_LE(r.from(), kMaxUInt16); + range_array->set(i * 2 + 0, r.from()); + const base::uc32 to = MaskEndOfRangeMarker(r.to()); + if (i == ranges_length - 1 && to == kMaxUInt16) { + DCHECK_EQ(range_array_length, ranges_length * 2 - 1); + break; // Avoid overflow by leaving the last range open-ended. + } + DCHECK_LT(to, kMaxUInt16); + range_array->set(i * 2 + 1, to + 1); // Exclusive. + } + return range_array; +} + +} // namespace + +Handle<ByteArray> NativeRegExpMacroAssembler::GetOrAddRangeArray( + const ZoneList<CharacterRange>* ranges) { + const uint32_t hash = Hash(ranges); + + if (range_array_cache_.count(hash) != 0) { + Handle<FixedUInt16Array> range_array = range_array_cache_[hash]; + if (Equals(ranges, range_array)) return range_array; + } + + Handle<FixedUInt16Array> range_array = MakeRangeArray(isolate(), ranges); + range_array_cache_[hash] = range_array; + return range_array; +} + +// static +uint32_t RegExpMacroAssembler::IsCharacterInRangeArray(uint32_t current_char, + Address raw_byte_array, + Isolate* isolate) { + // 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); + + // Shortcut for fully out of range chars. + 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; + } + + // 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(); + do { + mid = lower + (upper - lower) / 2; + 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 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; +} + +void RegExpMacroAssembler::CheckNotInSurrogatePair(int cp_offset, + Label* on_failure) { + Label ok; + // Check that current character is not a trail surrogate. + LoadCurrentCharacter(cp_offset, &ok); + CheckCharacterNotInRange(kTrailSurrogateStart, kTrailSurrogateEnd, &ok); + // Check that previous character is not a lead surrogate. + LoadCurrentCharacter(cp_offset - 1, &ok); + CheckCharacterInRange(kLeadSurrogateStart, kLeadSurrogateEnd, on_failure); + Bind(&ok); +} + +void RegExpMacroAssembler::CheckPosition(int cp_offset, + Label* on_outside_input) { + LoadCurrentCharacter(cp_offset, on_outside_input, true); +} + +void RegExpMacroAssembler::LoadCurrentCharacter(int cp_offset, + Label* on_end_of_input, + bool check_bounds, + int characters, + int eats_at_least) { + // By default, eats_at_least = characters. + if (eats_at_least == kUseCharactersValue) { + eats_at_least = characters; + } + + LoadCurrentCharacterImpl(cp_offset, on_end_of_input, check_bounds, characters, + eats_at_least); +} + +void NativeRegExpMacroAssembler::LoadCurrentCharacterImpl( + int cp_offset, Label* on_end_of_input, bool check_bounds, int characters, + int eats_at_least) { + // It's possible to preload a small number of characters when each success + // path requires a large number of characters, but not the reverse. + DCHECK_GE(eats_at_least, characters); + + DCHECK(base::IsInRange(cp_offset, kMinCPOffset, kMaxCPOffset)); + if (check_bounds) { + if (cp_offset >= 0) { + CheckPosition(cp_offset + eats_at_least - 1, on_end_of_input); + } else { + CheckPosition(cp_offset, on_end_of_input); + } + } + LoadCurrentCharacterUnchecked(cp_offset, characters); +} + +bool NativeRegExpMacroAssembler::CanReadUnaligned() const { + return v8_flags.enable_regexp_unaligned_accesses && !slow_safe(); +} + +#ifndef COMPILING_IRREGEXP_FOR_EXTERNAL_EMBEDDER + +// 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) { + 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()); + + StackLimitCheck check(isolate); + bool js_has_overflowed = check.JsHasOverflowed(); + + 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. + // 2. The stack guard was used to interrupt execution for another purpose, + // forcing the call through the runtime system. + + // Bug(v8:9540) Investigate why this method is called from JS although no + // stackoverflow or interrupt is pending on ARM64. We return 0 in this case + // to continue execution normally. + if (js_has_overflowed) { + return EXCEPTION; + } else if (check.InterruptRequested()) { + return RETRY; + } else { + return 0; + } + } + DCHECK(call_origin == RegExp::CallOrigin::kFromRuntime); + + // Prepare for possible GC. + HandleScope handles(isolate); + Handle<InstructionStream> code_handle(re_code, isolate); + Handle<String> subject_handle(String::cast(Object(*subject)), isolate); + bool is_one_byte = String::IsOneByteRepresentationUnderneath(*subject_handle); + int return_value = 0; + + { + DisableGCMole no_gc_mole; + if (js_has_overflowed) { + 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; + } + + // 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. + if (!code_handle->SafeEquals(re_code)) { // Return address no longer valid + // Overwrite the return address on the stack. + intptr_t delta = code_handle->address() - re_code.address(); + Address new_pc = old_pc + delta; + // TODO(v8:10026): avoid replacing a signed pointer. + PointerAuthentication::ReplacePC(return_address, new_pc, 0); + } + } + + // If we continue, we need to update the subject string addresses. + if (return_value == 0) { + // String encoding might have changed. + if (String::IsOneByteRepresentationUnderneath(*subject_handle) != + is_one_byte) { + // If we changed between an LATIN1 and an UC16 string, the specialized + // code cannot be used, and we need to restart regexp matching from + // scratch (including, potentially, compiling a new version of the code). + return_value = RETRY; + } else { + *subject = subject_handle->ptr(); + intptr_t byte_length = *input_end - *input_start; + *input_start = subject_handle->AddressOfCharacterAt(start_index, no_gc); + *input_end = *input_start + byte_length; + } + } + return return_value; +} + +// Returns a {Result} sentinel, or the number of successful matches. +int NativeRegExpMacroAssembler::Match(Handle<JSRegExp> regexp, + Handle<String> subject, + int* offsets_vector, + int offsets_vector_length, + int previous_index, Isolate* isolate) { + DCHECK(subject->IsFlat()); + DCHECK_LE(0, previous_index); + DCHECK_LE(previous_index, subject->length()); + + // 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; + // Character offsets into string. + int start_offset = previous_index; + 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(); + } else if (StringShape(subject_ptr).IsSliced()) { + SlicedString 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(); + } + // Ensure that an underlying string has the same representation. + bool is_one_byte = subject_ptr.IsOneByteRepresentation(); + DCHECK(subject_ptr.IsExternalString() || subject_ptr.IsSeqString()); + // 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); + 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, + const uint8_t* input_end, int* output, int output_size, Isolate* isolate, + JSRegExp 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. + int start_offset, const uint8_t* input_start, const uint8_t* input_end, + int* output, int output_size, Isolate* isolate, JSRegExp regexp) { + RegExpStackScope stack_scope(isolate); + + bool is_one_byte = String::IsOneByteRepresentationUnderneath(input); + Code 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, + const uint8_t* input_end, int* output, int output_size, + int call_origin, Isolate* isolate, Address regexp); + + auto fn = GeneratedCode<RegexpMatcherSig>::FromCode(isolate, code); + int result = fn.Call(input.ptr(), start_offset, input_start, input_end, + output, output_size, call_origin, isolate, regexp.ptr()); + DCHECK_GE(result, SMALLEST_REGEXP_RESULT); + + if (result == EXCEPTION && !isolate->has_pending_exception()) { + // We detected a stack overflow (on the backtrack stack) in RegExp code, + // but haven't created the exception yet. Additionally, we allow heap + // allocation because even though it invalidates {input_start} and + // {input_end}, we are about to return anyway. + AllowGarbageCollection allow_allocation; + isolate->StackOverflow(); + } + return result; +} + +#endif // !COMPILING_IRREGEXP_FOR_EXTERNAL_EMBEDDER + +// clang-format off +const uint8_t NativeRegExpMacroAssembler::word_character_map[] = { + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // '0' - '7' + 0xFFu, 0xFFu, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, // '8' - '9' + + 0x00u, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'A' - 'G' + 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'H' - 'O' + 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'P' - 'W' + 0xFFu, 0xFFu, 0xFFu, 0x00u, 0x00u, 0x00u, 0x00u, 0xFFu, // 'X' - 'Z', '_' + + 0x00u, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'a' - 'g' + 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'h' - 'o' + 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, // 'p' - 'w' + 0xFFu, 0xFFu, 0xFFu, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, // 'x' - 'z' + // Latin-1 range + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, + 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, 0x00u, +}; +// clang-format on + +// static +Address NativeRegExpMacroAssembler::GrowStack(Isolate* isolate) { + DisallowGarbageCollection no_gc; + + RegExpStack* regexp_stack = isolate->regexp_stack(); + const size_t old_size = regexp_stack->memory_size(); + +#ifdef DEBUG + const Address old_stack_top = regexp_stack->memory_top(); + const Address old_stack_pointer = regexp_stack->stack_pointer(); + CHECK_LE(old_stack_pointer, old_stack_top); + CHECK_LE(static_cast<size_t>(old_stack_top - old_stack_pointer), old_size); +#endif // DEBUG + + Address new_stack_base = regexp_stack->EnsureCapacity(old_size * 2); + if (new_stack_base == kNullAddress) return kNullAddress; + + return regexp_stack->stack_pointer(); +} + +} // namespace internal +} // namespace v8 diff --git a/js/src/irregexp/imported/regexp-macro-assembler.h b/js/src/irregexp/imported/regexp-macro-assembler.h new file mode 100644 index 0000000000..af7e4f5297 --- /dev/null +++ b/js/src/irregexp/imported/regexp-macro-assembler.h @@ -0,0 +1,361 @@ +// Copyright 2012 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. + +#ifndef V8_REGEXP_REGEXP_MACRO_ASSEMBLER_H_ +#define V8_REGEXP_REGEXP_MACRO_ASSEMBLER_H_ + +#include "irregexp/imported/regexp-ast.h" +#include "irregexp/imported/regexp.h" +#include "irregexp/RegExpShim.h" + +namespace v8 { +namespace internal { + +class ByteArray; +class JSRegExp; +class Label; +class String; + +static const base::uc32 kLeadSurrogateStart = 0xd800; +static const base::uc32 kLeadSurrogateEnd = 0xdbff; +static const base::uc32 kTrailSurrogateStart = 0xdc00; +static const base::uc32 kTrailSurrogateEnd = 0xdfff; +static const base::uc32 kNonBmpStart = 0x10000; +static const base::uc32 kNonBmpEnd = 0x10ffff; + +class RegExpMacroAssembler { + public: + // The implementation must be able to handle at least: + static constexpr int kMaxRegisterCount = (1 << 16); + static constexpr int kMaxRegister = kMaxRegisterCount - 1; + static constexpr int kMaxCaptures = (kMaxRegister - 1) / 2; + static constexpr int kMaxCPOffset = (1 << 15) - 1; + static constexpr int kMinCPOffset = -(1 << 15); + + static constexpr int kTableSizeBits = 7; + static constexpr int kTableSize = 1 << kTableSizeBits; + static constexpr int kTableMask = kTableSize - 1; + + static constexpr int kUseCharactersValue = -1; + + RegExpMacroAssembler(Isolate* isolate, Zone* zone); + virtual ~RegExpMacroAssembler() = default; + + virtual Handle<HeapObject> GetCode(Handle<String> source) = 0; + + // This function is called when code generation is aborted, so that + // the assembler could clean up internal data structures. + virtual void AbortedCodeGeneration() {} + // The maximal number of pushes between stack checks. Users must supply + // kCheckStackLimit flag to push operations (instead of kNoStackLimitCheck) + // at least once for every stack_limit() pushes that are executed. + virtual int stack_limit_slack() = 0; + virtual bool CanReadUnaligned() const = 0; + + virtual void AdvanceCurrentPosition(int by) = 0; // Signed cp change. + virtual void AdvanceRegister(int reg, int by) = 0; // r[reg] += by. + // Continues execution from the position pushed on the top of the backtrack + // stack by an earlier PushBacktrack(Label*). + virtual void Backtrack() = 0; + virtual void Bind(Label* label) = 0; + // Dispatch after looking the current character up in a 2-bits-per-entry + // map. The destinations vector has up to 4 labels. + virtual void CheckCharacter(unsigned c, Label* on_equal) = 0; + // Bitwise and the current character with the given constant and then + // check for a match with c. + virtual void CheckCharacterAfterAnd(unsigned c, + unsigned and_with, + Label* on_equal) = 0; + virtual void CheckCharacterGT(base::uc16 limit, Label* on_greater) = 0; + virtual void CheckCharacterLT(base::uc16 limit, Label* on_less) = 0; + virtual void CheckGreedyLoop(Label* on_tos_equals_current_position) = 0; + virtual void CheckAtStart(int cp_offset, Label* on_at_start) = 0; + virtual void CheckNotAtStart(int cp_offset, Label* on_not_at_start) = 0; + virtual void CheckNotBackReference(int start_reg, bool read_backward, + Label* on_no_match) = 0; + virtual void CheckNotBackReferenceIgnoreCase(int start_reg, + bool read_backward, bool unicode, + Label* on_no_match) = 0; + // Check the current character for a match with a literal character. If we + // fail to match then goto the on_failure label. End of input always + // matches. If the label is nullptr then we should pop a backtrack address + // off the stack and go to that. + virtual void CheckNotCharacter(unsigned c, Label* on_not_equal) = 0; + virtual void CheckNotCharacterAfterAnd(unsigned c, + unsigned and_with, + Label* on_not_equal) = 0; + // Subtract a constant from the current character, then and with the given + // constant and then check for a match with c. + virtual void CheckNotCharacterAfterMinusAnd(base::uc16 c, base::uc16 minus, + base::uc16 and_with, + Label* on_not_equal) = 0; + virtual void CheckCharacterInRange(base::uc16 from, + base::uc16 to, // Both inclusive. + Label* on_in_range) = 0; + virtual void CheckCharacterNotInRange(base::uc16 from, + base::uc16 to, // Both inclusive. + Label* on_not_in_range) = 0; + // Returns true if the check was emitted, false otherwise. + virtual bool CheckCharacterInRangeArray( + const ZoneList<CharacterRange>* ranges, Label* on_in_range) = 0; + virtual bool CheckCharacterNotInRangeArray( + const ZoneList<CharacterRange>* ranges, Label* on_not_in_range) = 0; + + // The current character (modulus the kTableSize) is looked up in the byte + // array, and if the found byte is non-zero, we jump to the on_bit_set label. + virtual void CheckBitInTable(Handle<ByteArray> table, Label* on_bit_set) = 0; + + // Checks whether the given offset from the current position is before + // the end of the string. May overwrite the current character. + virtual void CheckPosition(int cp_offset, Label* on_outside_input); + // Check whether a standard/default character class matches the current + // character. Returns false if the type of special character class does + // not have custom support. + // May clobber the current loaded character. + virtual bool CheckSpecialClassRanges(StandardCharacterSet type, + Label* on_no_match) { + return false; + } + + // Control-flow integrity: + // Define a jump target and bind a label. + virtual void BindJumpTarget(Label* label) { Bind(label); } + + virtual void Fail() = 0; + virtual void GoTo(Label* label) = 0; + // Check whether a register is >= a given constant and go to a label if it + // is. Backtracks instead if the label is nullptr. + virtual void IfRegisterGE(int reg, int comparand, Label* if_ge) = 0; + // Check whether a register is < a given constant and go to a label if it is. + // Backtracks instead if the label is nullptr. + virtual void IfRegisterLT(int reg, int comparand, Label* if_lt) = 0; + // Check whether a register is == to the current position and go to a + // label if it is. + virtual void IfRegisterEqPos(int reg, Label* if_eq) = 0; + V8_EXPORT_PRIVATE void LoadCurrentCharacter( + int cp_offset, Label* on_end_of_input, bool check_bounds = true, + int characters = 1, int eats_at_least = kUseCharactersValue); + virtual void LoadCurrentCharacterImpl(int cp_offset, Label* on_end_of_input, + bool check_bounds, int characters, + int eats_at_least) = 0; + virtual void PopCurrentPosition() = 0; + virtual void PopRegister(int register_index) = 0; + // Pushes the label on the backtrack stack, so that a following Backtrack + // will go to this label. Always checks the backtrack stack limit. + virtual void PushBacktrack(Label* label) = 0; + virtual void PushCurrentPosition() = 0; + enum StackCheckFlag { kNoStackLimitCheck = false, kCheckStackLimit = true }; + virtual void PushRegister(int register_index, + StackCheckFlag check_stack_limit) = 0; + virtual void ReadCurrentPositionFromRegister(int reg) = 0; + virtual void ReadStackPointerFromRegister(int reg) = 0; + virtual void SetCurrentPositionFromEnd(int by) = 0; + virtual void SetRegister(int register_index, int to) = 0; + // Return whether the matching (with a global regexp) will be restarted. + virtual bool Succeed() = 0; + virtual void WriteCurrentPositionToRegister(int reg, int cp_offset) = 0; + virtual void ClearRegisters(int reg_from, int reg_to) = 0; + virtual void WriteStackPointerToRegister(int reg) = 0; + + // Check that we are not in the middle of a surrogate pair. + void CheckNotInSurrogatePair(int cp_offset, Label* on_failure); + +#define IMPLEMENTATIONS_LIST(V) \ + V(IA32) \ + V(ARM) \ + V(ARM64) \ + V(MIPS) \ + V(LOONG64) \ + V(RISCV) \ + V(RISCV32) \ + V(S390) \ + V(PPC) \ + V(X64) \ + V(Bytecode) + + enum IrregexpImplementation { +#define V(Name) k##Name##Implementation, + IMPLEMENTATIONS_LIST(V) +#undef V + }; + + inline const char* ImplementationToString(IrregexpImplementation impl) { + static const char* const kNames[] = { +#define V(Name) #Name, + IMPLEMENTATIONS_LIST(V) +#undef V + }; + return kNames[impl]; + } +#undef IMPLEMENTATIONS_LIST + virtual IrregexpImplementation Implementation() = 0; + + // Compare two-byte strings case insensitively. + // + // Called from generated code. + static int CaseInsensitiveCompareNonUnicode(Address byte_offset1, + Address byte_offset2, + size_t byte_length, + Isolate* isolate); + static int CaseInsensitiveCompareUnicode(Address byte_offset1, + Address byte_offset2, + size_t byte_length, + Isolate* isolate); + + // `raw_byte_array` is a ByteArray containing a set of character ranges, + // where ranges are encoded as uint16_t elements: + // + // [from0, to0, from1, to1, ..., fromN, toN], or + // [from0, to0, from1, to1, ..., fromN] (open-ended last interval). + // + // fromN is inclusive, toN is exclusive. Returns zero if not in a range, + // non-zero otherwise. + // + // Called from generated code. + static uint32_t IsCharacterInRangeArray(uint32_t current_char, + Address raw_byte_array, + Isolate* isolate); + + // Controls the generation of large inlined constants in the code. + void set_slow_safe(bool ssc) { slow_safe_compiler_ = ssc; } + bool slow_safe() const { return slow_safe_compiler_; } + + // Controls after how many backtracks irregexp should abort execution. If it + // can fall back to the experimental engine (see `set_can_fallback`), it will + // return the appropriate error code, otherwise it will return the number of + // matches found so far (perhaps none). + void set_backtrack_limit(uint32_t backtrack_limit) { + backtrack_limit_ = backtrack_limit; + } + + // Set whether or not irregexp can fall back to the experimental engine on + // excessive backtracking. The number of backtracks considered excessive can + // be controlled with set_backtrack_limit. + void set_can_fallback(bool val) { can_fallback_ = val; } + + enum GlobalMode { + NOT_GLOBAL, + GLOBAL_NO_ZERO_LENGTH_CHECK, + GLOBAL, + GLOBAL_UNICODE + }; + // Set whether the regular expression has the global flag. Exiting due to + // a failure in a global regexp may still mean success overall. + inline void set_global_mode(GlobalMode mode) { global_mode_ = mode; } + inline bool global() const { return global_mode_ != NOT_GLOBAL; } + inline bool global_with_zero_length_check() const { + return global_mode_ == GLOBAL || global_mode_ == GLOBAL_UNICODE; + } + inline bool global_unicode() const { return global_mode_ == GLOBAL_UNICODE; } + + Isolate* isolate() const { return isolate_; } + Zone* zone() const { return zone_; } + + protected: + bool has_backtrack_limit() const; + uint32_t backtrack_limit() const { return backtrack_limit_; } + + bool can_fallback() const { return can_fallback_; } + + private: + bool slow_safe_compiler_; + uint32_t backtrack_limit_; + bool can_fallback_ = false; + GlobalMode global_mode_; + Isolate* const isolate_; + Zone* const zone_; +}; + +class NativeRegExpMacroAssembler: public RegExpMacroAssembler { + public: + // Type of input string to generate code for. + enum Mode { LATIN1 = 1, UC16 = 2 }; + + // Result of calling generated native RegExp code. + // RETRY: Something significant changed during execution, and the matching + // should be retried from scratch. + // EXCEPTION: Something failed during execution. If no exception has been + // thrown, it's an internal out-of-memory, and the caller should + // throw the exception. + // FAILURE: Matching failed. + // SUCCESS: Matching succeeded, and the output array has been filled with + // capture positions. + // FALLBACK_TO_EXPERIMENTAL: Execute the regexp on this subject using the + // experimental engine instead. + enum Result { + FAILURE = RegExp::kInternalRegExpFailure, + SUCCESS = RegExp::kInternalRegExpSuccess, + EXCEPTION = RegExp::kInternalRegExpException, + RETRY = RegExp::kInternalRegExpRetry, + FALLBACK_TO_EXPERIMENTAL = RegExp::kInternalRegExpFallbackToExperimental, + SMALLEST_REGEXP_RESULT = RegExp::kInternalRegExpSmallestResult, + }; + + NativeRegExpMacroAssembler(Isolate* isolate, Zone* zone) + : RegExpMacroAssembler(isolate, zone), range_array_cache_(zone) {} + ~NativeRegExpMacroAssembler() override = default; + + // Returns a {Result} sentinel, or the number of successful matches. + static int Match(Handle<JSRegExp> regexp, Handle<String> 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); + + bool CanReadUnaligned() const override; + + void LoadCurrentCharacterImpl(int cp_offset, Label* on_end_of_input, + bool check_bounds, int characters, + int eats_at_least) override; + // Load a number of characters at the given offset from the + // current position, into the current-character register. + virtual void LoadCurrentCharacterUnchecked(int cp_offset, + int character_count) = 0; + + // Called from RegExp if the backtrack stack limit is hit. Tries to expand + // the stack. Returns the new stack-pointer if successful, or returns 0 if + // unable to grow the stack. + // This function must not trigger a garbage collection. + // + // Called from generated code. + static Address GrowStack(Isolate* isolate); + + // 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); + + static Address word_character_map_address() { + return reinterpret_cast<Address>(&word_character_map[0]); + } + + protected: + // Byte map of one byte characters with a 0xff if the character is a word + // character (digit, letter or underscore) and 0x00 otherwise. + // Used by generated RegExp code. + static const uint8_t word_character_map[256]; + + Handle<ByteArray> GetOrAddRangeArray(const ZoneList<CharacterRange>* 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); + + ZoneUnorderedMap<uint32_t, Handle<FixedUInt16Array>> range_array_cache_; +}; + +} // namespace internal +} // namespace v8 + +#endif // V8_REGEXP_REGEXP_MACRO_ASSEMBLER_H_ diff --git a/js/src/irregexp/imported/regexp-nodes.h b/js/src/irregexp/imported/regexp-nodes.h new file mode 100644 index 0000000000..9407f1c5ec --- /dev/null +++ b/js/src/irregexp/imported/regexp-nodes.h @@ -0,0 +1,775 @@ +// 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. + +#ifndef V8_REGEXP_REGEXP_NODES_H_ +#define V8_REGEXP_REGEXP_NODES_H_ + +#include "irregexp/imported/regexp-macro-assembler.h" + +namespace v8 { +namespace internal { + +class AlternativeGenerationList; +class BoyerMooreLookahead; +class GreedyLoopState; +class NodeVisitor; +class QuickCheckDetails; +class RegExpCompiler; +class Trace; +struct PreloadState; +class ChoiceNode; + +#define FOR_EACH_NODE_TYPE(VISIT) \ + VISIT(End) \ + VISIT(Action) \ + VISIT(Choice) \ + VISIT(LoopChoice) \ + VISIT(NegativeLookaroundChoice) \ + VISIT(BackReference) \ + VISIT(Assertion) \ + VISIT(Text) + +struct NodeInfo final { + NodeInfo() + : being_analyzed(false), + been_analyzed(false), + follows_word_interest(false), + follows_newline_interest(false), + follows_start_interest(false), + at_end(false), + visited(false), + replacement_calculated(false) {} + + // Returns true if the interests and assumptions of this node + // matches the given one. + bool Matches(NodeInfo* that) { + return (at_end == that->at_end) && + (follows_word_interest == that->follows_word_interest) && + (follows_newline_interest == that->follows_newline_interest) && + (follows_start_interest == that->follows_start_interest); + } + + // Updates the interests of this node given the interests of the + // node preceding it. + void AddFromPreceding(NodeInfo* that) { + at_end |= that->at_end; + follows_word_interest |= that->follows_word_interest; + follows_newline_interest |= that->follows_newline_interest; + follows_start_interest |= that->follows_start_interest; + } + + bool HasLookbehind() { + return follows_word_interest || follows_newline_interest || + follows_start_interest; + } + + // Sets the interests of this node to include the interests of the + // following node. + void AddFromFollowing(NodeInfo* that) { + follows_word_interest |= that->follows_word_interest; + follows_newline_interest |= that->follows_newline_interest; + follows_start_interest |= that->follows_start_interest; + } + + void ResetCompilationState() { + being_analyzed = false; + been_analyzed = false; + } + + bool being_analyzed : 1; + bool been_analyzed : 1; + + // These bits are set of this node has to know what the preceding + // character was. + bool follows_word_interest : 1; + bool follows_newline_interest : 1; + bool follows_start_interest : 1; + + bool at_end : 1; + bool visited : 1; + bool replacement_calculated : 1; +}; + +struct EatsAtLeastInfo final { + EatsAtLeastInfo() : EatsAtLeastInfo(0) {} + explicit EatsAtLeastInfo(uint8_t eats) + : eats_at_least_from_possibly_start(eats), + eats_at_least_from_not_start(eats) {} + void SetMin(const EatsAtLeastInfo& other) { + if (other.eats_at_least_from_possibly_start < + eats_at_least_from_possibly_start) { + eats_at_least_from_possibly_start = + other.eats_at_least_from_possibly_start; + } + if (other.eats_at_least_from_not_start < eats_at_least_from_not_start) { + eats_at_least_from_not_start = other.eats_at_least_from_not_start; + } + } + + bool IsZero() const { + return eats_at_least_from_possibly_start == 0 && + eats_at_least_from_not_start == 0; + } + + // Any successful match starting from the current node will consume at least + // this many characters. This does not necessarily mean that there is a + // possible match with exactly this many characters, but we generally try to + // get this number as high as possible to allow for early exit on failure. + uint8_t eats_at_least_from_possibly_start; + + // Like eats_at_least_from_possibly_start, but with the additional assumption + // that start-of-string assertions (^) can't match. This value is greater than + // or equal to eats_at_least_from_possibly_start. + uint8_t eats_at_least_from_not_start; +}; + +class RegExpNode : public ZoneObject { + public: + explicit RegExpNode(Zone* zone) + : replacement_(nullptr), + on_work_list_(false), + trace_count_(0), + zone_(zone) { + bm_info_[0] = bm_info_[1] = nullptr; + } + virtual ~RegExpNode(); + virtual void Accept(NodeVisitor* visitor) = 0; + // Generates a goto to this node or actually generates the code at this point. + virtual void Emit(RegExpCompiler* compiler, Trace* trace) = 0; + // How many characters must this node consume at a minimum in order to + // succeed. The not_at_start argument is used to indicate that we know we are + // not at the start of the input. In this case anchored branches will always + // fail and can be ignored when determining how many characters are consumed + // on success. If this node has not been analyzed yet, EatsAtLeast returns 0. + int EatsAtLeast(bool not_at_start); + // Returns how many characters this node must consume in order to succeed, + // given that this is a LoopChoiceNode whose counter register is in a + // newly-initialized state at the current position in the generated code. For + // example, consider /a{6,8}/. Absent any extra information, the + // LoopChoiceNode for the repetition must report that it consumes at least + // zero characters, because it may have already looped several times. However, + // with a newly-initialized counter, it can report that it consumes at least + // six characters. + virtual EatsAtLeastInfo EatsAtLeastFromLoopEntry(); + // Emits some quick code that checks whether the preloaded characters match. + // Falls through on certain failure, jumps to the label on possible success. + // If the node cannot make a quick check it does nothing and returns false. + bool EmitQuickCheck(RegExpCompiler* compiler, Trace* bounds_check_trace, + Trace* trace, bool preload_has_checked_bounds, + Label* on_possible_success, + QuickCheckDetails* details_return, + bool fall_through_on_failure, ChoiceNode* predecessor); + // For a given number of characters this returns a mask and a value. The + // next n characters are anded with the mask and compared with the value. + // A comparison failure indicates the node cannot match the next n characters. + // A comparison success indicates the node may match. + virtual void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start) = 0; + // Fills in quick check details for this node, given that this is a + // LoopChoiceNode whose counter register is in a newly-initialized state at + // the current position in the generated code. For example, consider /a{6,8}/. + // Absent any extra information, the LoopChoiceNode for the repetition cannot + // generate any useful quick check because a match might be the (empty) + // continuation node. However, with a newly-initialized counter, it can + // generate a quick check for several 'a' characters at once. + virtual void GetQuickCheckDetailsFromLoopEntry(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start); + static const int kNodeIsTooComplexForGreedyLoops = kMinInt; + virtual int GreedyLoopTextLength() { return kNodeIsTooComplexForGreedyLoops; } + // Only returns the successor for a text node of length 1 that matches any + // character and that has no guards on it. + virtual RegExpNode* GetSuccessorOfOmnivorousTextNode( + RegExpCompiler* compiler) { + return nullptr; + } + + // Collects information on the possible code units (mod 128) that can match if + // we look forward. This is used for a Boyer-Moore-like string searching + // implementation. TODO(erikcorry): This should share more code with + // EatsAtLeast, GetQuickCheckDetails. The budget argument is used to limit + // the number of nodes we are willing to look at in order to create this data. + static const int kRecursionBudget = 200; + bool KeepRecursing(RegExpCompiler* compiler); + virtual void FillInBMInfo(Isolate* isolate, int offset, int budget, + BoyerMooreLookahead* bm, bool not_at_start) { + UNREACHABLE(); + } + + // If we know that the input is one-byte then there are some nodes that can + // never match. This method returns a node that can be substituted for + // itself, or nullptr if the node can never match. + virtual RegExpNode* FilterOneByte(int depth, RegExpFlags flags) { + return this; + } + // Helper for FilterOneByte. + RegExpNode* replacement() { + DCHECK(info()->replacement_calculated); + return replacement_; + } + RegExpNode* set_replacement(RegExpNode* replacement) { + info()->replacement_calculated = true; + replacement_ = replacement; + return replacement; // For convenience. + } + + // We want to avoid recalculating the lookahead info, so we store it on the + // node. Only info that is for this node is stored. We can tell that the + // info is for this node when offset == 0, so the information is calculated + // relative to this node. + void SaveBMInfo(BoyerMooreLookahead* bm, bool not_at_start, int offset) { + if (offset == 0) set_bm_info(not_at_start, bm); + } + + Label* label() { return &label_; } + // If non-generic code is generated for a node (i.e. the node is not at the + // start of the trace) then it cannot be reused. This variable sets a limit + // on how often we allow that to happen before we insist on starting a new + // trace and generating generic code for a node that can be reused by flushing + // the deferred actions in the current trace and generating a goto. + static const int kMaxCopiesCodeGenerated = 10; + + bool on_work_list() { return on_work_list_; } + void set_on_work_list(bool value) { on_work_list_ = value; } + + NodeInfo* info() { return &info_; } + const EatsAtLeastInfo* eats_at_least_info() const { return &eats_at_least_; } + void set_eats_at_least_info(const EatsAtLeastInfo& eats_at_least) { + eats_at_least_ = eats_at_least; + } + + // TODO(v8:10441): This is a hacky way to avoid exponential code size growth + // for very large choice nodes that can be generated by unicode property + // escapes. In order to avoid inlining (i.e. trace recursion), we pretend to + // have generated the maximum count of code copies already. + // We should instead fix this properly, e.g. by using the code size budget + // (flush_budget) or by generating property escape matches as calls to a C + // function. + void SetDoNotInline() { trace_count_ = kMaxCopiesCodeGenerated; } + + BoyerMooreLookahead* bm_info(bool not_at_start) { + return bm_info_[not_at_start ? 1 : 0]; + } + + Zone* zone() const { return zone_; } + + protected: + enum LimitResult { DONE, CONTINUE }; + RegExpNode* replacement_; + + LimitResult LimitVersions(RegExpCompiler* compiler, Trace* trace); + + void set_bm_info(bool not_at_start, BoyerMooreLookahead* bm) { + bm_info_[not_at_start ? 1 : 0] = bm; + } + + private: + static const int kFirstCharBudget = 10; + Label label_; + bool on_work_list_; + NodeInfo info_; + + // Saved values for EatsAtLeast results, to avoid recomputation. Filled in + // during analysis (valid if info_.been_analyzed is true). + EatsAtLeastInfo eats_at_least_; + + // This variable keeps track of how many times code has been generated for + // this node (in different traces). We don't keep track of where the + // generated code is located unless the code is generated at the start of + // a trace, in which case it is generic and can be reused by flushing the + // deferred operations in the current trace and generating a goto. + int trace_count_; + BoyerMooreLookahead* bm_info_[2]; + + Zone* zone_; +}; + +class SeqRegExpNode : public RegExpNode { + public: + explicit SeqRegExpNode(RegExpNode* on_success) + : RegExpNode(on_success->zone()), on_success_(on_success) {} + RegExpNode* on_success() { return on_success_; } + void set_on_success(RegExpNode* node) { on_success_ = node; } + RegExpNode* FilterOneByte(int depth, RegExpFlags flags) override; + void FillInBMInfo(Isolate* isolate, int offset, int budget, + BoyerMooreLookahead* bm, bool not_at_start) override { + on_success_->FillInBMInfo(isolate, offset, budget - 1, bm, not_at_start); + if (offset == 0) set_bm_info(not_at_start, bm); + } + + protected: + RegExpNode* FilterSuccessor(int depth, RegExpFlags flags); + + private: + RegExpNode* on_success_; +}; + +class ActionNode : public SeqRegExpNode { + public: + enum ActionType { + SET_REGISTER_FOR_LOOP, + INCREMENT_REGISTER, + STORE_POSITION, + BEGIN_POSITIVE_SUBMATCH, + BEGIN_NEGATIVE_SUBMATCH, + POSITIVE_SUBMATCH_SUCCESS, + EMPTY_MATCH_CHECK, + CLEAR_CAPTURES + }; + static ActionNode* SetRegisterForLoop(int reg, int val, + RegExpNode* on_success); + static ActionNode* IncrementRegister(int reg, RegExpNode* on_success); + static ActionNode* StorePosition(int reg, bool is_capture, + RegExpNode* on_success); + static ActionNode* ClearCaptures(Interval range, RegExpNode* on_success); + static ActionNode* BeginPositiveSubmatch(int stack_pointer_reg, + int position_reg, + RegExpNode* on_success); + static ActionNode* BeginNegativeSubmatch(int stack_pointer_reg, + int position_reg, + RegExpNode* on_success); + static ActionNode* PositiveSubmatchSuccess(int stack_pointer_reg, + int restore_reg, + int clear_capture_count, + int clear_capture_from, + RegExpNode* on_success); + static ActionNode* EmptyMatchCheck(int start_register, + int repetition_register, + int repetition_limit, + RegExpNode* on_success); + void Accept(NodeVisitor* visitor) override; + void Emit(RegExpCompiler* compiler, Trace* trace) override; + void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, int filled_in, + bool not_at_start) override; + void FillInBMInfo(Isolate* isolate, int offset, int budget, + BoyerMooreLookahead* bm, bool not_at_start) override; + ActionType action_type() { return action_type_; } + // TODO(erikcorry): We should allow some action nodes in greedy loops. + int GreedyLoopTextLength() override { + return kNodeIsTooComplexForGreedyLoops; + } + + private: + union { + struct { + int reg; + int value; + } u_store_register; + struct { + int reg; + } u_increment_register; + struct { + int reg; + bool is_capture; + } u_position_register; + struct { + int stack_pointer_register; + int current_position_register; + int clear_register_count; + int clear_register_from; + } u_submatch; + struct { + int start_register; + int repetition_register; + int repetition_limit; + } u_empty_match_check; + struct { + int range_from; + int range_to; + } u_clear_captures; + } data_; + ActionNode(ActionType action_type, RegExpNode* on_success) + : SeqRegExpNode(on_success), action_type_(action_type) {} + ActionType action_type_; + friend class DotPrinterImpl; + friend Zone; +}; + +class TextNode : public SeqRegExpNode { + public: + TextNode(ZoneList<TextElement>* elms, bool read_backward, + RegExpNode* on_success) + : SeqRegExpNode(on_success), elms_(elms), read_backward_(read_backward) {} + TextNode(RegExpClassRanges* that, bool read_backward, RegExpNode* on_success) + : SeqRegExpNode(on_success), + elms_(zone()->New<ZoneList<TextElement>>(1, zone())), + read_backward_(read_backward) { + elms_->Add(TextElement::ClassRanges(that), zone()); + } + // Create TextNode for a single character class for the given ranges. + static TextNode* CreateForCharacterRanges(Zone* zone, + ZoneList<CharacterRange>* ranges, + bool read_backward, + RegExpNode* on_success); + // Create TextNode for a surrogate pair (i.e. match a sequence of two uc16 + // code unit ranges). + static TextNode* CreateForSurrogatePair( + Zone* zone, CharacterRange lead, ZoneList<CharacterRange>* trail_ranges, + bool read_backward, RegExpNode* on_success); + static TextNode* CreateForSurrogatePair(Zone* zone, + ZoneList<CharacterRange>* lead_ranges, + CharacterRange trail, + bool read_backward, + RegExpNode* on_success); + void Accept(NodeVisitor* visitor) override; + void Emit(RegExpCompiler* compiler, Trace* trace) override; + void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, int characters_filled_in, + bool not_at_start) override; + ZoneList<TextElement>* elements() { return elms_; } + bool read_backward() { return read_backward_; } + void MakeCaseIndependent(Isolate* isolate, bool is_one_byte, + RegExpFlags flags); + int GreedyLoopTextLength() override; + RegExpNode* GetSuccessorOfOmnivorousTextNode( + RegExpCompiler* compiler) override; + void FillInBMInfo(Isolate* isolate, int offset, int budget, + BoyerMooreLookahead* bm, bool not_at_start) override; + void CalculateOffsets(); + RegExpNode* FilterOneByte(int depth, RegExpFlags flags) override; + int Length(); + + private: + enum TextEmitPassType { + NON_LATIN1_MATCH, // Check for characters that can't match. + SIMPLE_CHARACTER_MATCH, // Case-dependent single character check. + NON_LETTER_CHARACTER_MATCH, // Check characters that have no case equivs. + CASE_CHARACTER_MATCH, // Case-independent single character check. + CHARACTER_CLASS_MATCH // Character class. + }; + static bool SkipPass(TextEmitPassType pass, bool ignore_case); + static const int kFirstRealPass = SIMPLE_CHARACTER_MATCH; + static const int kLastPass = CHARACTER_CLASS_MATCH; + void TextEmitPass(RegExpCompiler* compiler, TextEmitPassType pass, + bool preloaded, Trace* trace, bool first_element_checked, + int* checked_up_to); + ZoneList<TextElement>* elms_; + bool read_backward_; +}; + +class AssertionNode : public SeqRegExpNode { + public: + enum AssertionType { + AT_END, + AT_START, + AT_BOUNDARY, + AT_NON_BOUNDARY, + AFTER_NEWLINE + }; + static AssertionNode* AtEnd(RegExpNode* on_success) { + return on_success->zone()->New<AssertionNode>(AT_END, on_success); + } + static AssertionNode* AtStart(RegExpNode* on_success) { + return on_success->zone()->New<AssertionNode>(AT_START, on_success); + } + static AssertionNode* AtBoundary(RegExpNode* on_success) { + return on_success->zone()->New<AssertionNode>(AT_BOUNDARY, on_success); + } + static AssertionNode* AtNonBoundary(RegExpNode* on_success) { + return on_success->zone()->New<AssertionNode>(AT_NON_BOUNDARY, on_success); + } + static AssertionNode* AfterNewline(RegExpNode* on_success) { + return on_success->zone()->New<AssertionNode>(AFTER_NEWLINE, on_success); + } + void Accept(NodeVisitor* visitor) override; + void Emit(RegExpCompiler* compiler, Trace* trace) override; + void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, int filled_in, + bool not_at_start) override; + void FillInBMInfo(Isolate* isolate, int offset, int budget, + BoyerMooreLookahead* bm, bool not_at_start) override; + AssertionType assertion_type() { return assertion_type_; } + + private: + friend Zone; + + void EmitBoundaryCheck(RegExpCompiler* compiler, Trace* trace); + enum IfPrevious { kIsNonWord, kIsWord }; + void BacktrackIfPrevious(RegExpCompiler* compiler, Trace* trace, + IfPrevious backtrack_if_previous); + AssertionNode(AssertionType t, RegExpNode* on_success) + : SeqRegExpNode(on_success), assertion_type_(t) {} + AssertionType assertion_type_; +}; + +class BackReferenceNode : public SeqRegExpNode { + public: + BackReferenceNode(int start_reg, int end_reg, RegExpFlags flags, + bool read_backward, RegExpNode* on_success) + : SeqRegExpNode(on_success), + start_reg_(start_reg), + end_reg_(end_reg), + flags_(flags), + read_backward_(read_backward) {} + void Accept(NodeVisitor* visitor) override; + int start_register() { return start_reg_; } + int end_register() { return end_reg_; } + bool read_backward() { return read_backward_; } + void Emit(RegExpCompiler* compiler, Trace* trace) override; + void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, int characters_filled_in, + bool not_at_start) override { + return; + } + void FillInBMInfo(Isolate* isolate, int offset, int budget, + BoyerMooreLookahead* bm, bool not_at_start) override; + + private: + int start_reg_; + int end_reg_; + RegExpFlags flags_; + bool read_backward_; +}; + +class EndNode : public RegExpNode { + public: + enum Action { ACCEPT, BACKTRACK, NEGATIVE_SUBMATCH_SUCCESS }; + EndNode(Action action, Zone* zone) : RegExpNode(zone), action_(action) {} + void Accept(NodeVisitor* visitor) override; + void Emit(RegExpCompiler* compiler, Trace* trace) override; + void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, int characters_filled_in, + bool not_at_start) override { + // Returning 0 from EatsAtLeast should ensure we never get here. + UNREACHABLE(); + } + void FillInBMInfo(Isolate* isolate, int offset, int budget, + BoyerMooreLookahead* bm, bool not_at_start) override { + // Returning 0 from EatsAtLeast should ensure we never get here. + UNREACHABLE(); + } + + private: + Action action_; +}; + +class NegativeSubmatchSuccess : public EndNode { + public: + NegativeSubmatchSuccess(int stack_pointer_reg, int position_reg, + int clear_capture_count, int clear_capture_start, + Zone* zone) + : EndNode(NEGATIVE_SUBMATCH_SUCCESS, zone), + stack_pointer_register_(stack_pointer_reg), + current_position_register_(position_reg), + clear_capture_count_(clear_capture_count), + clear_capture_start_(clear_capture_start) {} + void Emit(RegExpCompiler* compiler, Trace* trace) override; + + private: + int stack_pointer_register_; + int current_position_register_; + int clear_capture_count_; + int clear_capture_start_; +}; + +class Guard : public ZoneObject { + public: + enum Relation { LT, GEQ }; + Guard(int reg, Relation op, int value) : reg_(reg), op_(op), value_(value) {} + int reg() { return reg_; } + Relation op() { return op_; } + int value() { return value_; } + + private: + int reg_; + Relation op_; + int value_; +}; + +class GuardedAlternative { + public: + explicit GuardedAlternative(RegExpNode* node) + : node_(node), guards_(nullptr) {} + void AddGuard(Guard* guard, Zone* zone); + RegExpNode* node() { return node_; } + void set_node(RegExpNode* node) { node_ = node; } + ZoneList<Guard*>* guards() { return guards_; } + + private: + RegExpNode* node_; + ZoneList<Guard*>* guards_; +}; + +class AlternativeGeneration; + +class ChoiceNode : public RegExpNode { + public: + explicit ChoiceNode(int expected_size, Zone* zone) + : RegExpNode(zone), + alternatives_( + zone->New<ZoneList<GuardedAlternative>>(expected_size, zone)), + not_at_start_(false), + being_calculated_(false) {} + void Accept(NodeVisitor* visitor) override; + void AddAlternative(GuardedAlternative node) { + alternatives()->Add(node, zone()); + } + ZoneList<GuardedAlternative>* alternatives() { return alternatives_; } + void Emit(RegExpCompiler* compiler, Trace* trace) override; + void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, int characters_filled_in, + bool not_at_start) override; + void FillInBMInfo(Isolate* isolate, int offset, int budget, + BoyerMooreLookahead* bm, bool not_at_start) override; + + bool being_calculated() { return being_calculated_; } + bool not_at_start() { return not_at_start_; } + void set_not_at_start() { not_at_start_ = true; } + void set_being_calculated(bool b) { being_calculated_ = b; } + virtual bool try_to_emit_quick_check_for_alternative(bool is_first) { + return true; + } + RegExpNode* FilterOneByte(int depth, RegExpFlags flags) override; + virtual bool read_backward() { return false; } + + protected: + int GreedyLoopTextLengthForAlternative(GuardedAlternative* alternative); + ZoneList<GuardedAlternative>* alternatives_; + + private: + template <typename...> + friend class Analysis; + + void GenerateGuard(RegExpMacroAssembler* macro_assembler, Guard* guard, + Trace* trace); + int CalculatePreloadCharacters(RegExpCompiler* compiler, int eats_at_least); + void EmitOutOfLineContinuation(RegExpCompiler* compiler, Trace* trace, + GuardedAlternative alternative, + AlternativeGeneration* alt_gen, + int preload_characters, + bool next_expects_preload); + void SetUpPreLoad(RegExpCompiler* compiler, Trace* current_trace, + PreloadState* preloads); + void AssertGuardsMentionRegisters(Trace* trace); + int EmitOptimizedUnanchoredSearch(RegExpCompiler* compiler, Trace* trace); + Trace* EmitGreedyLoop(RegExpCompiler* compiler, Trace* trace, + AlternativeGenerationList* alt_gens, + PreloadState* preloads, + GreedyLoopState* greedy_loop_state, int text_length); + void EmitChoices(RegExpCompiler* compiler, + AlternativeGenerationList* alt_gens, int first_choice, + Trace* trace, PreloadState* preloads); + + // If true, this node is never checked at the start of the input. + // Allows a new trace to start with at_start() set to false. + bool not_at_start_; + bool being_calculated_; +}; + +class NegativeLookaroundChoiceNode : public ChoiceNode { + public: + explicit NegativeLookaroundChoiceNode(GuardedAlternative this_must_fail, + GuardedAlternative then_do_this, + Zone* zone) + : ChoiceNode(2, zone) { + AddAlternative(this_must_fail); + AddAlternative(then_do_this); + } + void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, int characters_filled_in, + bool not_at_start) override; + void FillInBMInfo(Isolate* isolate, int offset, int budget, + BoyerMooreLookahead* bm, bool not_at_start) override { + continue_node()->FillInBMInfo(isolate, offset, budget - 1, bm, + not_at_start); + if (offset == 0) set_bm_info(not_at_start, bm); + } + static constexpr int kLookaroundIndex = 0; + static constexpr int kContinueIndex = 1; + RegExpNode* lookaround_node() { + return alternatives()->at(kLookaroundIndex).node(); + } + RegExpNode* continue_node() { + return alternatives()->at(kContinueIndex).node(); + } + // For a negative lookahead we don't emit the quick check for the + // alternative that is expected to fail. This is because quick check code + // starts by loading enough characters for the alternative that takes fewest + // characters, but on a negative lookahead the negative branch did not take + // part in that calculation (EatsAtLeast) so the assumptions don't hold. + bool try_to_emit_quick_check_for_alternative(bool is_first) override { + return !is_first; + } + void Accept(NodeVisitor* visitor) override; + RegExpNode* FilterOneByte(int depth, RegExpFlags flags) override; +}; + +class LoopChoiceNode : public ChoiceNode { + public: + LoopChoiceNode(bool body_can_be_zero_length, bool read_backward, + int min_loop_iterations, Zone* zone) + : ChoiceNode(2, zone), + loop_node_(nullptr), + continue_node_(nullptr), + body_can_be_zero_length_(body_can_be_zero_length), + read_backward_(read_backward), + traversed_loop_initialization_node_(false), + min_loop_iterations_(min_loop_iterations) {} + void AddLoopAlternative(GuardedAlternative alt); + void AddContinueAlternative(GuardedAlternative alt); + void Emit(RegExpCompiler* compiler, Trace* trace) override; + void GetQuickCheckDetails(QuickCheckDetails* details, + RegExpCompiler* compiler, int characters_filled_in, + bool not_at_start) override; + void GetQuickCheckDetailsFromLoopEntry(QuickCheckDetails* details, + RegExpCompiler* compiler, + int characters_filled_in, + bool not_at_start) override; + void FillInBMInfo(Isolate* isolate, int offset, int budget, + BoyerMooreLookahead* bm, bool not_at_start) override; + EatsAtLeastInfo EatsAtLeastFromLoopEntry() override; + RegExpNode* loop_node() { return loop_node_; } + RegExpNode* continue_node() { return continue_node_; } + bool body_can_be_zero_length() { return body_can_be_zero_length_; } + int min_loop_iterations() const { return min_loop_iterations_; } + bool read_backward() override { return read_backward_; } + void Accept(NodeVisitor* visitor) override; + RegExpNode* FilterOneByte(int depth, RegExpFlags flags) override; + + private: + // AddAlternative is made private for loop nodes because alternatives + // should not be added freely, we need to keep track of which node + // goes back to the node itself. + void AddAlternative(GuardedAlternative node) { + ChoiceNode::AddAlternative(node); + } + + RegExpNode* loop_node_; + RegExpNode* continue_node_; + bool body_can_be_zero_length_; + bool read_backward_; + + // Temporary marker set only while generating quick check details. Represents + // whether GetQuickCheckDetails traversed the initialization node for this + // loop's counter. If so, we may be able to generate stricter quick checks + // because we know the loop node must match at least min_loop_iterations_ + // times before the continuation node can match. + bool traversed_loop_initialization_node_; + + // The minimum number of times the loop_node_ must match before the + // continue_node_ might be considered. This value can be temporarily decreased + // while generating quick check details, to represent the remaining iterations + // after the completed portion of the quick check details. + int min_loop_iterations_; + + friend class IterationDecrementer; + friend class LoopInitializationMarker; +}; + +class NodeVisitor { + public: + virtual ~NodeVisitor() = default; +#define DECLARE_VISIT(Type) virtual void Visit##Type(Type##Node* that) = 0; + FOR_EACH_NODE_TYPE(DECLARE_VISIT) +#undef DECLARE_VISIT +}; + +} // namespace internal +} // namespace v8 + +#endif // V8_REGEXP_REGEXP_NODES_H_ diff --git a/js/src/irregexp/imported/regexp-parser.cc b/js/src/irregexp/imported/regexp-parser.cc new file mode 100644 index 0000000000..ea2a6c6d7a --- /dev/null +++ b/js/src/irregexp/imported/regexp-parser.cc @@ -0,0 +1,3124 @@ +// Copyright 2016 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-parser.h" + +#include "irregexp/imported/regexp-ast.h" +#include "irregexp/imported/regexp-macro-assembler.h" +#include "irregexp/imported/regexp.h" + +#ifdef V8_INTL_SUPPORT +#include "unicode/uniset.h" +#include "unicode/unistr.h" +#include "unicode/usetiter.h" +#include "unicode/utf16.h" // For U16_NEXT +#endif // V8_INTL_SUPPORT + +namespace v8 { +namespace internal { + +namespace { + +// Whether we're currently inside the ClassEscape production +// (tc39.es/ecma262/#prod-annexB-CharacterEscape). +enum class InClassEscapeState { + kInClass, + kNotInClass, +}; + +// The production used to derive ClassSetOperand. +enum class ClassSetOperandType { + kClassSetCharacter, + kClassStringDisjunction, + kNestedClass, + kCharacterClassEscape, // \ CharacterClassEscape is a special nested class, + // as we can fold it directly into another range. + kClassSetRange +}; + +class RegExpTextBuilder { + public: + using SmallRegExpTreeVector = SmallZoneVector<RegExpTree*, 8>; + + RegExpTextBuilder(Zone* zone, SmallRegExpTreeVector* terms_storage, + RegExpFlags flags) + : zone_(zone), flags_(flags), terms_(terms_storage), text_(zone) {} + void AddCharacter(base::uc16 character); + void AddUnicodeCharacter(base::uc32 character); + void AddEscapedUnicodeCharacter(base::uc32 character); + void AddAtom(RegExpTree* atom); + void AddTerm(RegExpTree* term); + void AddClassRanges(RegExpClassRanges* cc); + void FlushPendingSurrogate(); + void FlushText(); + RegExpTree* PopLastAtom(); + RegExpTree* ToRegExp(); + + private: + static const base::uc16 kNoPendingSurrogate = 0; + + void AddLeadSurrogate(base::uc16 lead_surrogate); + void AddTrailSurrogate(base::uc16 trail_surrogate); + void FlushCharacters(); + bool NeedsDesugaringForUnicode(RegExpClassRanges* cc); + 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 + return IsUnicode(flags_) || IsUnicodeSets(flags_); + } + Zone* zone() const { return zone_; } + + Zone* const zone_; + const RegExpFlags flags_; + ZoneList<base::uc16>* characters_ = nullptr; + base::uc16 pending_surrogate_ = kNoPendingSurrogate; + SmallRegExpTreeVector* terms_; + SmallRegExpTreeVector text_; +}; + +void RegExpTextBuilder::AddLeadSurrogate(base::uc16 lead_surrogate) { + DCHECK(unibrow::Utf16::IsLeadSurrogate(lead_surrogate)); + FlushPendingSurrogate(); + // Hold onto the lead surrogate, waiting for a trail surrogate to follow. + pending_surrogate_ = lead_surrogate; +} + +void RegExpTextBuilder::AddTrailSurrogate(base::uc16 trail_surrogate) { + DCHECK(unibrow::Utf16::IsTrailSurrogate(trail_surrogate)); + if (pending_surrogate_ != kNoPendingSurrogate) { + base::uc16 lead_surrogate = pending_surrogate_; + pending_surrogate_ = kNoPendingSurrogate; + DCHECK(unibrow::Utf16::IsLeadSurrogate(lead_surrogate)); + base::uc32 combined = + unibrow::Utf16::CombineSurrogatePair(lead_surrogate, trail_surrogate); + if (NeedsDesugaringForIgnoreCase(combined)) { + AddClassRangesForDesugaring(combined); + } else { + ZoneList<base::uc16> surrogate_pair(2, zone()); + surrogate_pair.Add(lead_surrogate, zone()); + surrogate_pair.Add(trail_surrogate, zone()); + RegExpAtom* atom = + zone()->New<RegExpAtom>(surrogate_pair.ToConstVector()); + AddAtom(atom); + } + } else { + pending_surrogate_ = trail_surrogate; + FlushPendingSurrogate(); + } +} + +void RegExpTextBuilder::FlushPendingSurrogate() { + if (pending_surrogate_ != kNoPendingSurrogate) { + DCHECK(IsUnicodeMode()); + base::uc32 c = pending_surrogate_; + pending_surrogate_ = kNoPendingSurrogate; + AddClassRangesForDesugaring(c); + } +} + +void RegExpTextBuilder::FlushCharacters() { + FlushPendingSurrogate(); + if (characters_ != nullptr) { + RegExpTree* atom = zone()->New<RegExpAtom>(characters_->ToConstVector()); + characters_ = nullptr; + text_.emplace_back(atom); + } +} + +void RegExpTextBuilder::FlushText() { + FlushCharacters(); + size_t num_text = text_.size(); + if (num_text == 0) { + return; + } else if (num_text == 1) { + terms_->emplace_back(text_.back()); + } else { + RegExpText* text = zone()->New<RegExpText>(zone()); + for (size_t i = 0; i < num_text; i++) { + text_[i]->AppendToText(text, zone()); + } + terms_->emplace_back(text); + } + text_.clear(); +} + +void RegExpTextBuilder::AddCharacter(base::uc16 c) { + FlushPendingSurrogate(); + if (NeedsDesugaringForIgnoreCase(c)) { + AddClassRangesForDesugaring(c); + } else { + if (characters_ == nullptr) { + characters_ = zone()->New<ZoneList<base::uc16>>(4, zone()); + } + characters_->Add(c, zone()); + } +} + +void RegExpTextBuilder::AddUnicodeCharacter(base::uc32 c) { + if (c > static_cast<base::uc32>(unibrow::Utf16::kMaxNonSurrogateCharCode)) { + DCHECK(IsUnicodeMode()); + AddLeadSurrogate(unibrow::Utf16::LeadSurrogate(c)); + AddTrailSurrogate(unibrow::Utf16::TrailSurrogate(c)); + } else if (IsUnicodeMode() && unibrow::Utf16::IsLeadSurrogate(c)) { + AddLeadSurrogate(c); + } else if (IsUnicodeMode() && unibrow::Utf16::IsTrailSurrogate(c)) { + AddTrailSurrogate(c); + } else { + AddCharacter(static_cast<base::uc16>(c)); + } +} + +void RegExpTextBuilder::AddEscapedUnicodeCharacter(base::uc32 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 RegExpTextBuilder::AddClassRanges(RegExpClassRanges* cr) { + if (NeedsDesugaringForUnicode(cr)) { + // With /u or /v, character class needs to be desugared, so it + // must be a standalone term instead of being part of a RegExpText. + AddTerm(cr); + } else { + AddAtom(cr); + } +} + +void RegExpTextBuilder::AddClassRangesForDesugaring(base::uc32 c) { + AddTerm(zone()->New<RegExpClassRanges>( + zone(), CharacterRange::List(zone(), CharacterRange::Singleton(c)))); +} + +void RegExpTextBuilder::AddAtom(RegExpTree* atom) { + DCHECK(atom->IsTextElement()); + FlushCharacters(); + text_.emplace_back(atom); +} + +void RegExpTextBuilder::AddTerm(RegExpTree* term) { + DCHECK(term->IsTextElement()); + FlushText(); + terms_->emplace_back(term); +} + +bool RegExpTextBuilder::NeedsDesugaringForUnicode(RegExpClassRanges* cc) { + if (!IsUnicodeMode()) 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; + ZoneList<CharacterRange>* ranges = cc->ranges(zone()); + CharacterRange::Canonicalize(ranges); + + if (cc->is_negated()) { + ZoneList<CharacterRange>* negated_ranges = + zone()->New<ZoneList<CharacterRange>>(ranges->length(), zone()); + CharacterRange::Negate(ranges, negated_ranges, zone()); + ranges = negated_ranges; + } + + for (int i = ranges->length() - 1; i >= 0; i--) { + base::uc32 from = ranges->at(i).from(); + base::uc32 to = ranges->at(i).to(); + // Check for non-BMP characters. + if (to >= kNonBmpStart) return true; + // Check for lone surrogates. + if (from <= kTrailSurrogateEnd && to >= kLeadSurrogateStart) return true; + } + return false; +} + +bool RegExpTextBuilder::NeedsDesugaringForIgnoreCase(base::uc32 c) { +#ifdef V8_INTL_SUPPORT + if (IsUnicodeMode() && ignore_case()) { + icu::UnicodeSet set(c, c); + set.closeOver(USET_CASE_INSENSITIVE); + set.removeAllStrings(); + return set.size() > 1; + } + // In the case where ICU is not included, we act as if the unicode flag is + // not set, and do not desugar. +#endif // V8_INTL_SUPPORT + return false; +} + +RegExpTree* RegExpTextBuilder::PopLastAtom() { + FlushPendingSurrogate(); + RegExpTree* atom; + if (characters_ != nullptr) { + base::Vector<const base::uc16> char_vector = characters_->ToConstVector(); + int num_chars = char_vector.length(); + if (num_chars > 1) { + base::Vector<const base::uc16> prefix = + char_vector.SubVector(0, num_chars - 1); + text_.emplace_back(zone()->New<RegExpAtom>(prefix)); + char_vector = char_vector.SubVector(num_chars - 1, num_chars); + } + characters_ = nullptr; + atom = zone()->New<RegExpAtom>(char_vector); + return atom; + } else if (text_.size() > 0) { + atom = text_.back(); + text_.pop_back(); + return atom; + } + return nullptr; +} + +RegExpTree* RegExpTextBuilder::ToRegExp() { + FlushText(); + size_t num_alternatives = terms_->size(); + if (num_alternatives == 0) return zone()->New<RegExpEmpty>(); + if (num_alternatives == 1) return terms_->back(); + return zone()->New<RegExpAlternative>(zone()->New<ZoneList<RegExpTree*>>( + base::VectorOf(terms_->begin(), terms_->size()), zone())); +} + +// Accumulates RegExp atoms and assertions into lists of terms and alternatives. +class RegExpBuilder { + public: + RegExpBuilder(Zone* zone, RegExpFlags flags) + : zone_(zone), + flags_(flags), + terms_(zone), + alternatives_(zone), + text_builder_(RegExpTextBuilder{zone, &terms_, flags}) {} + void AddCharacter(base::uc16 character); + void AddUnicodeCharacter(base::uc32 character); + void AddEscapedUnicodeCharacter(base::uc32 character); + // "Adds" an empty expression. Does nothing except consume a + // following quantifier + void AddEmpty(); + void AddClassRanges(RegExpClassRanges* cc); + void AddAtom(RegExpTree* tree); + void AddTerm(RegExpTree* tree); + void AddAssertion(RegExpTree* tree); + void NewAlternative(); // '|' + bool AddQuantifierToAtom(int min, int max, + RegExpQuantifier::QuantifierType type); + void FlushText(); + RegExpTree* ToRegExp(); + RegExpFlags flags() const { return flags_; } + + bool ignore_case() const { return IsIgnoreCase(flags_); } + bool multiline() const { return IsMultiline(flags_); } + bool dotall() const { return IsDotAll(flags_); } + + 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 + return IsUnicode(flags_) || IsUnicodeSets(flags_); + } + Zone* zone() const { return zone_; } + RegExpTextBuilder& text_builder() { return text_builder_; } + + Zone* const zone_; + bool pending_empty_ = false; + const RegExpFlags flags_; + + using SmallRegExpTreeVector = SmallZoneVector<RegExpTree*, 8>; + SmallRegExpTreeVector terms_; + SmallRegExpTreeVector alternatives_; + RegExpTextBuilder text_builder_; +}; + +enum SubexpressionType { + INITIAL, + CAPTURE, // All positive values represent captures. + POSITIVE_LOOKAROUND, + NEGATIVE_LOOKAROUND, + GROUPING +}; + +class RegExpParserState : public ZoneObject { + public: + // Push a state on the stack. + RegExpParserState(RegExpParserState* previous_state, + SubexpressionType group_type, + RegExpLookaround::Type lookaround_type, + int disjunction_capture_index, + const ZoneVector<base::uc16>* capture_name, + RegExpFlags flags, Zone* zone) + : previous_state_(previous_state), + builder_(zone, flags), + group_type_(group_type), + lookaround_type_(lookaround_type), + disjunction_capture_index_(disjunction_capture_index), + capture_name_(capture_name) {} + // Parser state of containing expression, if any. + RegExpParserState* previous_state() const { return previous_state_; } + bool IsSubexpression() { return previous_state_ != nullptr; } + // RegExpBuilder building this regexp's AST. + RegExpBuilder* builder() { return &builder_; } + // Type of regexp being parsed (parenthesized group or entire regexp). + SubexpressionType group_type() const { return group_type_; } + // Lookahead or Lookbehind. + RegExpLookaround::Type lookaround_type() const { return lookaround_type_; } + // Index in captures array of first capture in this sub-expression, if any. + // Also the capture index of this sub-expression itself, if group_type + // is CAPTURE. + int capture_index() const { return disjunction_capture_index_; } + // The name of the current sub-expression, if group_type is CAPTURE. Only + // used for named captures. + const ZoneVector<base::uc16>* capture_name() const { return capture_name_; } + + bool IsNamedCapture() const { return capture_name_ != nullptr; } + + // Check whether the parser is inside a capture group with the given index. + bool IsInsideCaptureGroup(int index) const { + for (const 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; + } + + // Check whether the parser is inside a capture group with the given name. + bool IsInsideCaptureGroup(const ZoneVector<base::uc16>* name) const { + DCHECK_NOT_NULL(name); + for (const RegExpParserState* s = this; s != nullptr; + s = s->previous_state()) { + if (s->capture_name() == nullptr) continue; + if (*s->capture_name() == *name) return true; + } + return false; + } + + private: + // Linked list implementation of stack of states. + RegExpParserState* const previous_state_; + // Builder for the stored disjunction. + RegExpBuilder builder_; + // Stored disjunction type (capture, look-ahead or grouping), if any. + const SubexpressionType group_type_; + // Stored read direction. + const RegExpLookaround::Type lookaround_type_; + // Stored disjunction's capture index (if any). + const int disjunction_capture_index_; + // Stored capture name (if any). + const ZoneVector<base::uc16>* const capture_name_; +}; + +template <class CharT> +class RegExpParserImpl final { + private: + RegExpParserImpl(const CharT* input, int input_length, RegExpFlags flags, + uintptr_t stack_limit, Zone* zone, + const DisallowGarbageCollection& no_gc); + + bool Parse(RegExpCompileData* result); + + RegExpTree* ParsePattern(); + RegExpTree* ParseDisjunction(); + RegExpTree* ParseGroup(); + + // Parses a {...,...} quantifier and stores the range in the given + // out parameters. + bool ParseIntervalQuantifier(int* min_out, int* max_out); + + // Checks whether the following is a length-digit hexadecimal number, + // and sets the value if it is. + bool ParseHexEscape(int length, base::uc32* value); + bool ParseUnicodeEscape(base::uc32* value); + bool ParseUnlimitedLengthHexNumber(int max_value, base::uc32* value); + + bool ParsePropertyClassName(ZoneVector<char>* name_1, + ZoneVector<char>* name_2); + bool AddPropertyClassRange(ZoneList<CharacterRange>* add_to_range, + CharacterClassStrings* add_to_strings, bool negate, + const ZoneVector<char>& name_1, + const ZoneVector<char>& name_2); + + RegExpTree* ParseClassRanges(ZoneList<CharacterRange>* ranges, + bool add_unicode_case_equivalents); + // Parse inside a class. Either add escaped class to the range, or return + // false and pass parsed single character through |char_out|. + void ParseClassEscape(ZoneList<CharacterRange>* ranges, Zone* zone, + bool add_unicode_case_equivalents, base::uc32* char_out, + bool* is_class_escape); + // Returns true iff parsing was successful. + bool TryParseCharacterClassEscape(base::uc32 next, + InClassEscapeState in_class_escape_state, + ZoneList<CharacterRange>* ranges, + CharacterClassStrings* strings, Zone* zone, + bool add_unicode_case_equivalents); + RegExpTree* ParseClassStringDisjunction(ZoneList<CharacterRange>* ranges, + CharacterClassStrings* strings); + RegExpTree* ParseClassSetOperand(const RegExpBuilder* builder, + ClassSetOperandType* type_out); + RegExpTree* ParseClassSetOperand(const RegExpBuilder* builder, + ClassSetOperandType* type_out, + ZoneList<CharacterRange>* ranges, + CharacterClassStrings* strings); + base::uc32 ParseClassSetCharacter(); + // Parses and returns a single escaped character. + base::uc32 ParseCharacterEscape(InClassEscapeState in_class_escape_state, + bool* is_escaped_unicode_character); + + RegExpTree* ParseClassUnion(const RegExpBuilder* builder, bool is_negated, + RegExpTree* first_operand, + ClassSetOperandType first_operand_type, + ZoneList<CharacterRange>* ranges, + CharacterClassStrings* strings); + 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, + ClassSetOperandType first_operand_type); + RegExpTree* ParseCharacterClass(const RegExpBuilder* state); + + base::uc32 ParseOctalLiteral(); + + // Tries to parse the input as a back reference. If successful it + // stores the result in the output parameter and returns true. If + // it fails it will push back the characters read so the same characters + // can be reparsed. + bool ParseBackReferenceIndex(int* index_out); + + RegExpTree* ReportError(RegExpError error); + void Advance(); + void Advance(int dist); + void RewindByOneCodepoint(); // Rewinds to before the previous Advance(). + void Reset(int pos); + + // Reports whether the pattern might be used as a literal search string. + // Only use if the result of the parse is a single atom node. + bool simple() const { return simple_; } + bool contains_anchor() const { return contains_anchor_; } + void set_contains_anchor() { contains_anchor_ = true; } + int captures_started() const { return captures_started_; } + 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 + return IsUnicode(flags()) || IsUnicodeSets(flags()) || force_unicode_; + } + bool unicode_sets() const { return IsUnicodeSets(flags()); } + bool ignore_case() const { return IsIgnoreCase(flags()); } + + static bool IsSyntaxCharacterOrSlash(base::uc32 c); + static bool IsClassSetSyntaxCharacter(base::uc32 c); + static bool IsClassSetReservedPunctuator(base::uc32 c); + bool IsClassSetReservedDoublePunctuator(base::uc32 c); + + static const base::uc32 kEndMarker = (1 << 21); + + private: + // Return the 1-indexed RegExpCapture object, allocate if necessary. + RegExpCapture* GetCapture(int index); + + // Creates a new named capture at the specified index. Must be called exactly + // once for each named capture. Fails if a capture with the same name is + // encountered. + bool CreateNamedCaptureAtIndex(const ZoneVector<base::uc16>* name, int index); + + // Parses the name of a capture group (?<name>pattern). The name must adhere + // to IdentifierName in the ECMAScript standard. + const ZoneVector<base::uc16>* ParseCaptureGroupName(); + + bool ParseNamedBackReference(RegExpBuilder* builder, + RegExpParserState* state); + RegExpParserState* ParseOpenParenthesis(RegExpParserState* state); + + // After the initial parsing pass, patch corresponding RegExpCapture objects + // into all RegExpBackReferences. This is done after initial parsing in order + // to avoid complicating cases in which references comes before the capture. + void PatchNamedBackReferences(); + + ZoneVector<RegExpCapture*>* GetNamedCaptures() const; + + // Returns true iff the pattern contains named captures. May call + // ScanForCaptures to look ahead at the remaining pattern. + bool HasNamedCaptures(InClassEscapeState in_class_escape_state); + + Zone* zone() const { return zone_; } + + base::uc32 current() const { return current_; } + bool has_more() const { return has_more_; } + bool has_next() const { return next_pos_ < input_length(); } + base::uc32 Next(); + template <bool update_position> + base::uc32 ReadNext(); + CharT InputAt(int index) const { + DCHECK(0 <= index && index < input_length()); + return input_[index]; + } + int input_length() const { return input_length_; } + void ScanForCaptures(InClassEscapeState in_class_escape_state); + + struct RegExpCaptureNameLess { + bool operator()(const RegExpCapture* lhs, const RegExpCapture* rhs) const { + DCHECK_NOT_NULL(lhs); + DCHECK_NOT_NULL(rhs); + return *lhs->name() < *rhs->name(); + } + }; + + class ForceUnicodeScope final { + public: + explicit ForceUnicodeScope(RegExpParserImpl<CharT>* parser) + : parser_(parser) { + DCHECK(!parser_->force_unicode_); + parser_->force_unicode_ = true; + } + ~ForceUnicodeScope() { + DCHECK(parser_->force_unicode_); + parser_->force_unicode_ = false; + } + + private: + RegExpParserImpl<CharT>* const parser_; + }; + + const DisallowGarbageCollection no_gc_; + Zone* const zone_; + RegExpError error_ = RegExpError::kNone; + int error_pos_ = 0; + ZoneList<RegExpCapture*>* captures_; + ZoneSet<RegExpCapture*, RegExpCaptureNameLess>* named_captures_; + ZoneList<RegExpBackReference*>* named_back_references_; + 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. + int next_pos_; + int captures_started_; + int capture_count_; // Only valid after we have scanned for captures. + bool has_more_; + bool simple_; + bool contains_anchor_; + bool is_scanned_for_captures_; + bool has_named_captures_; // Only valid after we have scanned for captures. + bool failed_; + const uintptr_t stack_limit_; + + friend class v8::internal::RegExpParser; +}; + +template <class CharT> +RegExpParserImpl<CharT>::RegExpParserImpl( + const CharT* input, int input_length, RegExpFlags flags, + uintptr_t stack_limit, Zone* zone, const DisallowGarbageCollection& no_gc) + : zone_(zone), + captures_(nullptr), + named_captures_(nullptr), + named_back_references_(nullptr), + input_(input), + input_length_(input_length), + current_(kEndMarker), + top_level_flags_(flags), + next_pos_(0), + captures_started_(0), + capture_count_(0), + has_more_(true), + simple_(false), + contains_anchor_(false), + is_scanned_for_captures_(false), + has_named_captures_(false), + failed_(false), + stack_limit_(stack_limit) { + Advance(); +} + +template <> +template <bool update_position> +inline base::uc32 RegExpParserImpl<uint8_t>::ReadNext() { + int position = next_pos_; + base::uc16 c0 = InputAt(position); + position++; + DCHECK(!unibrow::Utf16::IsLeadSurrogate(c0)); + if (update_position) next_pos_ = position; + return c0; +} + +template <> +template <bool update_position> +inline base::uc32 RegExpParserImpl<base::uc16>::ReadNext() { + int position = next_pos_; + base::uc16 c0 = InputAt(position); + base::uc32 result = c0; + position++; + // Read the whole surrogate pair in case of unicode mode, if possible. + if (IsUnicodeMode() && position < input_length() && + unibrow::Utf16::IsLeadSurrogate(c0)) { + base::uc16 c1 = InputAt(position); + if (unibrow::Utf16::IsTrailSurrogate(c1)) { + result = unibrow::Utf16::CombineSurrogatePair(c0, c1); + position++; + } + } + if (update_position) next_pos_ = position; + return result; +} + +template <class CharT> +base::uc32 RegExpParserImpl<CharT>::Next() { + if (has_next()) { + return ReadNext<false>(); + } else { + return kEndMarker; + } +} + +template <class CharT> +void RegExpParserImpl<CharT>::Advance() { + if (has_next()) { + if (GetCurrentStackPosition() < stack_limit_) { + if (v8_flags.correctness_fuzzer_suppressions) { + FATAL("Aborting on stack overflow"); + } + ReportError(RegExpError::kStackOverflow); + } else { + 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_ = input_length() + 1; + has_more_ = false; + } +} + +template <class CharT> +void RegExpParserImpl<CharT>::RewindByOneCodepoint() { + if (!has_more()) return; + // Rewinds by one code point, i.e.: two code units if `current` is outside + // the basic multilingual plane (= composed of a lead and trail surrogate), + // or one code unit otherwise. + const int rewind_by = + current() > unibrow::Utf16::kMaxNonSurrogateCharCode ? -2 : -1; + Advance(rewind_by); // Undo the last Advance. +} + +template <class CharT> +void RegExpParserImpl<CharT>::Reset(int pos) { + next_pos_ = pos; + has_more_ = (pos < input_length()); + Advance(); +} + +template <class CharT> +void RegExpParserImpl<CharT>::Advance(int dist) { + next_pos_ += dist - 1; + Advance(); +} + +// static +template <class CharT> +bool RegExpParserImpl<CharT>::IsSyntaxCharacterOrSlash(base::uc32 c) { + switch (c) { + case '^': + case '$': + case '\\': + case '.': + case '*': + case '+': + case '?': + case '(': + case ')': + case '[': + case ']': + case '{': + case '}': + case '|': + case '/': + return true; + default: + break; + } + return false; +} + +// static +template <class CharT> +bool RegExpParserImpl<CharT>::IsClassSetSyntaxCharacter(base::uc32 c) { + switch (c) { + case '(': + case ')': + case '[': + case ']': + case '{': + case '}': + case '/': + case '-': + case '\\': + case '|': + return true; + default: + break; + } + return false; +} + +// static +template <class CharT> +bool RegExpParserImpl<CharT>::IsClassSetReservedPunctuator(base::uc32 c) { + switch (c) { + case '&': + case '-': + case '!': + case '#': + case '%': + case ',': + case ':': + case ';': + case '<': + case '=': + case '>': + case '@': + case '`': + case '~': + return true; + default: + break; + } + return false; +} + +template <class CharT> +bool RegExpParserImpl<CharT>::IsClassSetReservedDoublePunctuator(base::uc32 c) { +#define DOUBLE_PUNCTUATOR_CASE(Char) \ + case Char: \ + return Next() == Char + + switch (c) { + DOUBLE_PUNCTUATOR_CASE('&'); + DOUBLE_PUNCTUATOR_CASE('!'); + DOUBLE_PUNCTUATOR_CASE('#'); + DOUBLE_PUNCTUATOR_CASE('$'); + DOUBLE_PUNCTUATOR_CASE('%'); + DOUBLE_PUNCTUATOR_CASE('*'); + DOUBLE_PUNCTUATOR_CASE('+'); + DOUBLE_PUNCTUATOR_CASE(','); + DOUBLE_PUNCTUATOR_CASE('.'); + DOUBLE_PUNCTUATOR_CASE(':'); + DOUBLE_PUNCTUATOR_CASE(';'); + DOUBLE_PUNCTUATOR_CASE('<'); + DOUBLE_PUNCTUATOR_CASE('='); + DOUBLE_PUNCTUATOR_CASE('>'); + DOUBLE_PUNCTUATOR_CASE('?'); + DOUBLE_PUNCTUATOR_CASE('@'); + DOUBLE_PUNCTUATOR_CASE('^'); + DOUBLE_PUNCTUATOR_CASE('`'); + DOUBLE_PUNCTUATOR_CASE('~'); + default: + break; + } +#undef DOUBLE_PUNCTUATOR_CASE + + return false; +} + +template <class CharT> +RegExpTree* RegExpParserImpl<CharT>::ReportError(RegExpError error) { + if (failed_) return nullptr; // Do not overwrite any existing error. + failed_ = true; + error_ = error; + error_pos_ = position(); + // Zip to the end to make sure no more input is read. + current_ = kEndMarker; + next_pos_ = input_length(); + has_more_ = false; + return nullptr; +} + +#define CHECK_FAILED /**/); \ + if (failed_) return nullptr; \ + ((void)0 + +// Pattern :: +// Disjunction +template <class CharT> +RegExpTree* RegExpParserImpl<CharT>::ParsePattern() { + RegExpTree* result = ParseDisjunction(CHECK_FAILED); + PatchNamedBackReferences(CHECK_FAILED); + DCHECK(!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() == input_length()) { + simple_ = true; + } + return result; +} + +// Disjunction :: +// Alternative +// Alternative | Disjunction +// Alternative :: +// [empty] +// Term Alternative +// Term :: +// Assertion +// Atom +// Atom Quantifier +template <class CharT> +RegExpTree* RegExpParserImpl<CharT>::ParseDisjunction() { + // Used to store current state while parsing subexpressions. + RegExpParserState initial_state(nullptr, INITIAL, RegExpLookaround::LOOKAHEAD, + 0, nullptr, flags(), zone()); + RegExpParserState* 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 (failed()) return nullptr; // E.g. the initial Advance failed. + if (state->IsSubexpression()) { + // Inside a parenthesized group when hitting end of input. + return ReportError(RegExpError::kUnterminatedGroup); + } + DCHECK_EQ(INITIAL, state->group_type()); + // Parsing completed successfully. + return builder->ToRegExp(); + case ')': { + if (!state->IsSubexpression()) { + return ReportError(RegExpError::kUnmatchedParen); + } + DCHECK_NE(INITIAL, state->group_type()); + + Advance(); + // End disjunction parsing and convert builder content to new single + // regexp atom. + RegExpTree* body = builder->ToRegExp(); + + int end_capture_index = captures_started(); + + int capture_index = state->capture_index(); + SubexpressionType group_type = state->group_type(); + + // Build result of subexpression. + if (group_type == CAPTURE) { + if (state->IsNamedCapture()) { + CreateNamedCaptureAtIndex(state->capture_name(), + capture_index CHECK_FAILED); + } + RegExpCapture* capture = GetCapture(capture_index); + capture->set_body(body); + body = capture; + } else if (group_type == GROUPING) { + body = zone()->template New<RegExpGroup>(body); + } else { + DCHECK(group_type == POSITIVE_LOOKAROUND || + group_type == NEGATIVE_LOOKAROUND); + bool is_positive = (group_type == POSITIVE_LOOKAROUND); + body = zone()->template New<RegExpLookaround>( + body, is_positive, end_capture_index - capture_index, + capture_index, state->lookaround_type()); + } + + // Restore previous state. + state = state->previous_state(); + builder = 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 '?': + return ReportError(RegExpError::kNothingToRepeat); + case '^': { + Advance(); + builder->AddAssertion(zone()->template New<RegExpAssertion>( + builder->multiline() ? RegExpAssertion::Type::START_OF_LINE + : RegExpAssertion::Type::START_OF_INPUT)); + set_contains_anchor(); + continue; + } + case '$': { + Advance(); + RegExpAssertion::Type assertion_type = + builder->multiline() ? RegExpAssertion::Type::END_OF_LINE + : RegExpAssertion::Type::END_OF_INPUT; + builder->AddAssertion( + zone()->template New<RegExpAssertion>(assertion_type)); + continue; + } + case '.': { + Advance(); + ZoneList<CharacterRange>* ranges = + zone()->template New<ZoneList<CharacterRange>>(2, zone()); + + if (builder->dotall()) { + // Everything. + CharacterRange::AddClassEscape(StandardCharacterSet::kEverything, + ranges, false, zone()); + } else { + // Everything except \x0A, \x0D, \u2028 and \u2029. + CharacterRange::AddClassEscape( + StandardCharacterSet::kNotLineTerminator, ranges, false, zone()); + } + + RegExpClassRanges* cc = + zone()->template New<RegExpClassRanges>(zone(), ranges); + builder->AddClassRanges(cc); + break; + } + case '(': { + state = ParseOpenParenthesis(state CHECK_FAILED); + builder = state->builder(); + continue; + } + case '[': { + RegExpTree* cc = ParseCharacterClass(builder CHECK_FAILED); + if (cc->IsClassRanges()) { + builder->AddClassRanges(cc->AsClassRanges()); + } else { + DCHECK(cc->IsClassSetExpression()); + builder->AddTerm(cc); + } + break; + } + // Atom :: + // \ AtomEscape + case '\\': + switch (Next()) { + case kEndMarker: + return ReportError(RegExpError::kEscapeAtEndOfPattern); + // AtomEscape :: + // [+UnicodeMode] DecimalEscape + // [~UnicodeMode] DecimalEscape but only if the CapturingGroupNumber + // of DecimalEscape is ≤ NcapturingParens + // CharacterEscape (some cases of this mixed in too) + // + // TODO(jgruber): It may make sense to disentangle all the different + // cases and make the structure mirror the spec, e.g. for AtomEscape: + // + // if (TryParseDecimalEscape(...)) return; + // if (TryParseCharacterClassEscape(...)) return; + // if (TryParseCharacterEscape(...)) return; + // if (TryParseGroupName(...)) return; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': { + int index = 0; + const bool is_backref = + ParseBackReferenceIndex(&index CHECK_FAILED); + if (is_backref) { + if (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 = zone()->template New<RegExpBackReference>( + capture, builder->flags()); + builder->AddAtom(atom); + } + break; + } + // With /u and /v, no identity escapes except for syntax characters + // are allowed. Otherwise, all identity escapes are allowed. + if (IsUnicodeMode()) { + return ReportError(RegExpError::kInvalidEscape); + } + base::uc32 first_digit = Next(); + if (first_digit == '8' || first_digit == '9') { + builder->AddCharacter(first_digit); + Advance(2); + break; + } + V8_FALLTHROUGH; + } + case '0': { + Advance(); + if (IsUnicodeMode() && Next() >= '0' && Next() <= '9') { + // Decimal escape with leading 0 are not parsed as octal. + return ReportError(RegExpError::kInvalidDecimalEscape); + } + base::uc32 octal = ParseOctalLiteral(); + builder->AddCharacter(octal); + break; + } + case 'b': + Advance(2); + builder->AddAssertion(zone()->template New<RegExpAssertion>( + RegExpAssertion::Type::BOUNDARY)); + continue; + case 'B': + Advance(2); + builder->AddAssertion(zone()->template New<RegExpAssertion>( + RegExpAssertion::Type::NON_BOUNDARY)); + continue; + // AtomEscape :: + // CharacterClassEscape + case 'd': + case 'D': + case 's': + case 'S': + case 'w': + case 'W': { + base::uc32 next = Next(); + ZoneList<CharacterRange>* ranges = + zone()->template New<ZoneList<CharacterRange>>(2, zone()); + bool add_unicode_case_equivalents = + IsUnicodeMode() && ignore_case(); + bool parsed_character_class_escape = TryParseCharacterClassEscape( + next, InClassEscapeState::kNotInClass, ranges, nullptr, zone(), + add_unicode_case_equivalents CHECK_FAILED); + + if (parsed_character_class_escape) { + RegExpClassRanges* cc = + zone()->template New<RegExpClassRanges>(zone(), ranges); + builder->AddClassRanges(cc); + } else { + CHECK(!IsUnicodeMode()); + Advance(2); + builder->AddCharacter(next); // IdentityEscape. + } + break; + } + case 'p': + case 'P': { + base::uc32 next = Next(); + ZoneList<CharacterRange>* ranges = + zone()->template New<ZoneList<CharacterRange>>(2, zone()); + CharacterClassStrings* strings = nullptr; + if (unicode_sets()) { + strings = zone()->template New<CharacterClassStrings>(zone()); + } + bool add_unicode_case_equivalents = ignore_case(); + bool parsed_character_class_escape = TryParseCharacterClassEscape( + next, InClassEscapeState::kNotInClass, ranges, strings, zone(), + add_unicode_case_equivalents CHECK_FAILED); + + if (parsed_character_class_escape) { + if (unicode_sets()) { + RegExpClassSetOperand* op = + zone()->template New<RegExpClassSetOperand>(ranges, + strings); + builder->AddTerm(op); + } else { + RegExpClassRanges* cc = + zone()->template New<RegExpClassRanges>(zone(), ranges); + builder->AddClassRanges(cc); + } + } else { + CHECK(!IsUnicodeMode()); + Advance(2); + builder->AddCharacter(next); // IdentityEscape. + } + break; + } + // AtomEscape :: + // k GroupName + 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. + const bool has_named_captures = + HasNamedCaptures(InClassEscapeState::kNotInClass CHECK_FAILED); + if (IsUnicodeMode() || has_named_captures) { + Advance(2); + ParseNamedBackReference(builder, state CHECK_FAILED); + break; + } + } + V8_FALLTHROUGH; + // AtomEscape :: + // CharacterEscape + default: { + bool is_escaped_unicode_character = false; + base::uc32 c = ParseCharacterEscape( + InClassEscapeState::kNotInClass, + &is_escaped_unicode_character CHECK_FAILED); + if (is_escaped_unicode_character) { + builder->AddEscapedUnicodeCharacter(c); + } else { + builder->AddCharacter(c); + } + break; + } + } + break; + case '{': { + int dummy; + bool parsed = ParseIntervalQuantifier(&dummy, &dummy CHECK_FAILED); + if (parsed) return ReportError(RegExpError::kNothingToRepeat); + V8_FALLTHROUGH; + } + case '}': + case ']': + if (IsUnicodeMode()) { + return ReportError(RegExpError::kLoneQuantifierBrackets); + } + V8_FALLTHROUGH; + default: + builder->AddUnicodeCharacter(current()); + Advance(); + break; + } // end switch(current()) + + int min; + int 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) { + return ReportError(RegExpError::kRangeOutOfOrder); + } + break; + } else if (IsUnicodeMode()) { + // Incomplete quantifiers are not allowed. + return ReportError(RegExpError::kIncompleteQuantifier); + } + continue; + default: + continue; + } + RegExpQuantifier::QuantifierType quantifier_type = RegExpQuantifier::GREEDY; + if (current() == '?') { + quantifier_type = RegExpQuantifier::NON_GREEDY; + Advance(); + } else if (v8_flags.regexp_possessive_quantifier && current() == '+') { + // v8_flags.regexp_possessive_quantifier is a debug-only flag. + quantifier_type = RegExpQuantifier::POSSESSIVE; + Advance(); + } + if (!builder->AddQuantifierToAtom(min, max, quantifier_type)) { + return ReportError(RegExpError::kInvalidQuantifier); + } + } +} + +template <class CharT> +RegExpParserState* RegExpParserImpl<CharT>::ParseOpenParenthesis( + RegExpParserState* state) { + RegExpLookaround::Type lookaround_type = state->lookaround_type(); + bool is_named_capture = false; + const ZoneVector<base::uc16>* 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(RegExpError::kInvalidGroup); + return nullptr; + } + } + if (subexpr_type == CAPTURE) { + if (captures_started_ >= RegExpMacroAssembler::kMaxCaptures) { + ReportError(RegExpError::kTooManyCaptures); + return nullptr; + } + captures_started_++; + + if (is_named_capture) { + capture_name = ParseCaptureGroupName(CHECK_FAILED); + } + } + // Store current state and begin new disjunction parsing. + return zone()->template New<RegExpParserState>( + state, subexpr_type, lookaround_type, captures_started_, capture_name, + state->builder()->flags(), zone()); +} + +// 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. +// +// Important: The scanner has to be in a consistent state when calling +// ScanForCaptures, e.g. not in the middle of an escape sequence '\[' or while +// parsing a nested class. +template <class CharT> +void RegExpParserImpl<CharT>::ScanForCaptures( + InClassEscapeState in_class_escape_state) { + DCHECK(!is_scanned_for_captures_); + const int saved_position = position(); + // Start with captures started previous to current position + int capture_count = captures_started(); + // When we start inside a character class, skip everything inside the class. + if (in_class_escape_state == InClassEscapeState::kInClass) { + // \k is always invalid within a class in unicode mode, thus we should never + // call ScanForCaptures within a class. + DCHECK(!IsUnicodeMode()); + int c; + while ((c = current()) != kEndMarker) { + Advance(); + if (c == '\\') { + Advance(); + } else { + if (c == ']') break; + } + } + } + // Add count of captures after this position. + int n; + while ((n = current()) != kEndMarker) { + Advance(); + switch (n) { + case '\\': + Advance(); + break; + case '[': { + int class_nest_level = 0; + int c; + while ((c = current()) != kEndMarker) { + Advance(); + if (c == '\\') { + Advance(); + } else if (c == '[') { + // With /v, '[' inside a class is treated as a nested class. + // Without /v, '[' is a normal character. + if (unicode_sets()) class_nest_level++; + } else if (c == ']') { + if (class_nest_level == 0) break; + class_nest_level--; + } + } + break; + } + case '(': + if (current() == '?') { + // At this point we could be in + // * a non-capturing group '(:', + // * a lookbehind assertion '(?<=' '(?<!' + // * or a named capture '(?<'. + // + // Of these, only named captures are capturing groups. + + Advance(); + if (current() != '<') break; + + Advance(); + if (current() == '=' || current() == '!') break; + + // Found a possible named capture. It could turn out to be a syntax + // error (e.g. an unterminated or invalid name), but that distinction + // does not matter for our purposes. + has_named_captures_ = true; + } + capture_count++; + break; + } + } + capture_count_ = capture_count; + is_scanned_for_captures_ = true; + Reset(saved_position); +} + +template <class CharT> +bool RegExpParserImpl<CharT>::ParseBackReferenceIndex(int* index_out) { + DCHECK_EQ('\\', current()); + DCHECK('1' <= Next() && Next() <= '9'); + // Try to parse a decimal literal that is no greater than the total number + // of left capturing parentheses in the input. + int start = position(); + int value = Next() - '0'; + Advance(2); + while (true) { + base::uc32 c = current(); + if (IsDecimalDigit(c)) { + value = 10 * value + (c - '0'); + if (value > RegExpMacroAssembler::kMaxCaptures) { + Reset(start); + return false; + } + Advance(); + } else { + break; + } + } + if (value > captures_started()) { + if (!is_scanned_for_captures_) { + ScanForCaptures(InClassEscapeState::kNotInClass); + } + if (value > capture_count_) { + Reset(start); + return false; + } + } + *index_out = value; + return true; +} + +namespace { + +void push_code_unit(ZoneVector<base::uc16>* v, uint32_t code_unit) { + if (code_unit <= unibrow::Utf16::kMaxNonSurrogateCharCode) { + v->push_back(code_unit); + } else { + v->push_back(unibrow::Utf16::LeadSurrogate(code_unit)); + v->push_back(unibrow::Utf16::TrailSurrogate(code_unit)); + } +} + +} // namespace + +template <class CharT> +const ZoneVector<base::uc16>* RegExpParserImpl<CharT>::ParseCaptureGroupName() { + // Due to special Advance requirements (see the next comment), rewind by one + // such that names starting with a surrogate pair are parsed correctly for + // patterns where the unicode flag is unset. + // + // Note that we use this odd pattern of rewinding the last advance in order + // to adhere to the common parser behavior of expecting `current` to point at + // the first candidate character for a function (e.g. when entering ParseFoo, + // `current` should point at the first character of Foo). + RewindByOneCodepoint(); + + ZoneVector<base::uc16>* name = + zone()->template New<ZoneVector<base::uc16>>(zone()); + + { + // Advance behavior inside this function is tricky since + // RegExpIdentifierName explicitly enables unicode (in spec terms, sets +U) + // and thus allows surrogate pairs and \u{}-style escapes even in + // non-unicode patterns. Therefore Advance within the capture group name + // has to force-enable unicode, and outside the name revert to default + // behavior. + ForceUnicodeScope force_unicode(this); + + bool at_start = true; + while (true) { + Advance(); + base::uc32 c = current(); + + // Convert unicode escapes. + if (c == '\\' && Next() == 'u') { + Advance(2); + if (!ParseUnicodeEscape(&c)) { + ReportError(RegExpError::kInvalidUnicodeEscape); + return nullptr; + } + RewindByOneCodepoint(); + } + + // The backslash char is misclassified as both ID_Start and ID_Continue. + if (c == '\\') { + ReportError(RegExpError::kInvalidCaptureGroupName); + return nullptr; + } + + if (at_start) { + if (!IsIdentifierStart(c)) { + ReportError(RegExpError::kInvalidCaptureGroupName); + return nullptr; + } + push_code_unit(name, c); + at_start = false; + } else { + if (c == '>') { + break; + } else if (IsIdentifierPart(c)) { + push_code_unit(name, c); + } else { + ReportError(RegExpError::kInvalidCaptureGroupName); + return nullptr; + } + } + } + } + + // This final advance goes back into the state of pointing at the next + // relevant char, which the rest of the parser expects. See also the previous + // comments in this function. + Advance(); + return name; +} + +template <class CharT> +bool RegExpParserImpl<CharT>::CreateNamedCaptureAtIndex( + const ZoneVector<base::uc16>* name, int index) { + DCHECK(0 < index && index <= captures_started_); + DCHECK_NOT_NULL(name); + + RegExpCapture* capture = GetCapture(index); + DCHECK_NULL(capture->name()); + + capture->set_name(name); + + if (named_captures_ == nullptr) { + named_captures_ = + zone_->template New<ZoneSet<RegExpCapture*, RegExpCaptureNameLess>>( + zone()); + } else { + // Check for duplicates and bail if we find any. + + const auto& named_capture_it = named_captures_->find(capture); + if (named_capture_it != named_captures_->end()) { + ReportError(RegExpError::kDuplicateCaptureGroupName); + return false; + } + } + + named_captures_->emplace(capture); + + return true; +} + +template <class CharT> +bool RegExpParserImpl<CharT>::ParseNamedBackReference( + RegExpBuilder* builder, RegExpParserState* state) { + // The parser is assumed to be on the '<' in \k<name>. + if (current() != '<') { + ReportError(RegExpError::kInvalidNamedReference); + return false; + } + + Advance(); + const ZoneVector<base::uc16>* name = ParseCaptureGroupName(); + if (name == nullptr) { + return false; + } + + if (state->IsInsideCaptureGroup(name)) { + builder->AddEmpty(); + } else { + RegExpBackReference* atom = + zone()->template New<RegExpBackReference>(builder->flags()); + atom->set_name(name); + + builder->AddAtom(atom); + + if (named_back_references_ == nullptr) { + named_back_references_ = + zone()->template New<ZoneList<RegExpBackReference*>>(1, zone()); + } + named_back_references_->Add(atom, zone()); + } + + return true; +} + +template <class CharT> +void RegExpParserImpl<CharT>::PatchNamedBackReferences() { + if (named_back_references_ == nullptr) return; + + if (named_captures_ == nullptr) { + ReportError(RegExpError::kInvalidNamedCaptureReference); + return; + } + + // Look up and patch the actual capture for each named back reference. + + for (int i = 0; i < named_back_references_->length(); i++) { + RegExpBackReference* ref = named_back_references_->at(i); + + // Capture used to search the named_captures_ by name, index of the + // capture is never used. + static const int kInvalidIndex = 0; + RegExpCapture* search_capture = + zone()->template New<RegExpCapture>(kInvalidIndex); + DCHECK_NULL(search_capture->name()); + search_capture->set_name(ref->name()); + + int index = -1; + const auto& capture_it = named_captures_->find(search_capture); + if (capture_it != named_captures_->end()) { + index = (*capture_it)->index(); + } else { + ReportError(RegExpError::kInvalidNamedCaptureReference); + return; + } + + ref->set_capture(GetCapture(index)); + } +} + +template <class CharT> +RegExpCapture* RegExpParserImpl<CharT>::GetCapture(int index) { + // The index for the capture groups are one-based. Its index in the list is + // zero-based. + const int known_captures = + is_scanned_for_captures_ ? capture_count_ : captures_started_; + DCHECK(index <= known_captures); + if (captures_ == nullptr) { + captures_ = + zone()->template New<ZoneList<RegExpCapture*>>(known_captures, zone()); + } + while (captures_->length() < known_captures) { + captures_->Add(zone()->template New<RegExpCapture>(captures_->length() + 1), + zone()); + } + return captures_->at(index - 1); +} + +template <class CharT> +ZoneVector<RegExpCapture*>* RegExpParserImpl<CharT>::GetNamedCaptures() const { + if (named_captures_ == nullptr || named_captures_->empty()) { + return nullptr; + } + + return zone()->template New<ZoneVector<RegExpCapture*>>( + named_captures_->begin(), named_captures_->end(), zone()); +} + +template <class CharT> +bool RegExpParserImpl<CharT>::HasNamedCaptures( + InClassEscapeState in_class_escape_state) { + if (has_named_captures_ || is_scanned_for_captures_) { + return has_named_captures_; + } + + ScanForCaptures(in_class_escape_state); + DCHECK(is_scanned_for_captures_); + return has_named_captures_; +} + +// 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. +template <class CharT> +bool RegExpParserImpl<CharT>::ParseIntervalQuantifier(int* min_out, + int* max_out) { + DCHECK_EQ(current(), '{'); + int start = position(); + Advance(); + int min = 0; + if (!IsDecimalDigit(current())) { + Reset(start); + return false; + } + while (IsDecimalDigit(current())) { + int next = current() - '0'; + if (min > (RegExpTree::kInfinity - next) / 10) { + // Overflow. Skip past remaining decimal digits and return -1. + do { + Advance(); + } while (IsDecimalDigit(current())); + min = RegExpTree::kInfinity; + break; + } + min = 10 * min + next; + Advance(); + } + int max = 0; + if (current() == '}') { + max = min; + Advance(); + } else if (current() == ',') { + Advance(); + if (current() == '}') { + max = RegExpTree::kInfinity; + Advance(); + } else { + while (IsDecimalDigit(current())) { + int next = current() - '0'; + if (max > (RegExpTree::kInfinity - next) / 10) { + do { + Advance(); + } while (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; +} + +template <class CharT> +base::uc32 RegExpParserImpl<CharT>::ParseOctalLiteral() { + DCHECK(('0' <= current() && current() <= '7') || !has_more()); + // For compatibility with some other browsers (not all), we parse + // up to three octal digits with a value below 256. + // ES#prod-annexB-LegacyOctalEscapeSequence + base::uc32 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; +} + +template <class CharT> +bool RegExpParserImpl<CharT>::ParseHexEscape(int length, base::uc32* value) { + int start = position(); + base::uc32 val = 0; + for (int i = 0; i < length; ++i) { + base::uc32 c = current(); + int d = base::HexValue(c); + if (d < 0) { + Reset(start); + return false; + } + val = val * 16 + d; + Advance(); + } + *value = val; + return true; +} + +// This parses RegExpUnicodeEscapeSequence as described in ECMA262. +template <class CharT> +bool RegExpParserImpl<CharT>::ParseUnicodeEscape(base::uc32* 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() == '{' && IsUnicodeMode()) { + int start = position(); + Advance(); + if (ParseUnlimitedLengthHexNumber(0x10FFFF, 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 && IsUnicodeMode() && unibrow::Utf16::IsLeadSurrogate(*value) && + current() == '\\') { + // Attempt to read trail surrogate. + int start = position(); + if (Next() == 'u') { + Advance(2); + base::uc32 trail; + if (ParseHexEscape(4, &trail) && + unibrow::Utf16::IsTrailSurrogate(trail)) { + *value = unibrow::Utf16::CombineSurrogatePair( + static_cast<base::uc16>(*value), static_cast<base::uc16>(trail)); + return true; + } + } + Reset(start); + } + return result; +} + +#ifdef V8_INTL_SUPPORT + +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<UPropertyNameChoice>(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<UPropertyNameChoice>(U_LONG_PROPERTY_NAME + i)); + if (long_name == nullptr) break; + if (strcmp(property_value_name, long_name) == 0) return true; + } + return false; +} + +void ExtractStringsFromUnicodeSet(const icu::UnicodeSet& set, + CharacterClassStrings* strings, + RegExpFlags flags, Zone* zone) { + DCHECK(set.hasStrings()); + DCHECK(IsUnicodeSets(flags)); + DCHECK_NOT_NULL(strings); + + RegExpTextBuilder::SmallRegExpTreeVector string_storage(zone); + RegExpTextBuilder string_builder(zone, &string_storage, flags); + const bool needs_case_folding = IsIgnoreCase(flags); + icu::UnicodeSetIterator iter(set); + iter.skipToStrings(); + while (iter.next()) { + const icu::UnicodeString& s = iter.getString(); + const char16_t* p = s.getBuffer(); + int32_t length = s.length(); + ZoneList<base::uc32>* string = + zone->template New<ZoneList<base::uc32>>(length, zone); + for (int32_t i = 0; i < length;) { + UChar32 c; + U16_NEXT(p, i, length, c); + string_builder.AddUnicodeCharacter(c); + if (needs_case_folding) { + c = u_foldCase(c, U_FOLD_CASE_DEFAULT); + } + string->Add(c, zone); + } + strings->emplace(string->ToVector(), string_builder.ToRegExp()); + string_storage.clear(); + } +} + +bool LookupPropertyValueName(UProperty property, + const char* property_value_name, bool negate, + ZoneList<CharacterRange>* result_ranges, + CharacterClassStrings* result_strings, + RegExpFlags flags, Zone* zone) { + 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(); + + 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); + 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)), + zone); + } + } + return success; +} + +template <size_t N> +inline bool NameEquals(const char* name, const char (&literal)[N]) { + return strncmp(name, literal, N + 1) == 0; +} + +bool LookupSpecialPropertyValueName(const char* name, + ZoneList<CharacterRange>* result, + bool negate, RegExpFlags flags, + Zone* zone) { + 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(), zone); + } + } else if (NameEquals(name, "ASCII")) { + result->Add(negate ? CharacterRange::Range(0x80, String::kMaxCodePoint) + : CharacterRange::Range(0x0, 0x7F), + zone); + } else if (NameEquals(name, "Assigned")) { + return LookupPropertyValueName(UCHAR_GENERAL_CATEGORY, "Unassigned", + !negate, result, nullptr, flags, zone); + } else { + return false; + } + return true; +} + +// Explicitly allowlist supported binary properties. The spec forbids supporting +// properties outside of this set to ensure interoperability. +bool IsSupportedBinaryProperty(UProperty property, bool unicode_sets) { + 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; + case UCHAR_BASIC_EMOJI: + case UCHAR_EMOJI_KEYCAP_SEQUENCE: + case UCHAR_RGI_EMOJI_MODIFIER_SEQUENCE: + case UCHAR_RGI_EMOJI_FLAG_SEQUENCE: + case UCHAR_RGI_EMOJI_TAG_SEQUENCE: + case UCHAR_RGI_EMOJI_ZWJ_SEQUENCE: + case UCHAR_RGI_EMOJI: + return unicode_sets; + default: + break; + } + return false; +} + +bool IsBinaryPropertyOfStrings(UProperty property) { + switch (property) { + case UCHAR_BASIC_EMOJI: + case UCHAR_EMOJI_KEYCAP_SEQUENCE: + case UCHAR_RGI_EMOJI_MODIFIER_SEQUENCE: + case UCHAR_RGI_EMOJI_FLAG_SEQUENCE: + case UCHAR_RGI_EMOJI_TAG_SEQUENCE: + case UCHAR_RGI_EMOJI_ZWJ_SEQUENCE: + case UCHAR_RGI_EMOJI: + 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 ('a' <= c && c <= 'z') return true; + if ('A' <= c && c <= 'Z') return true; + if ('0' <= c && c <= '9') return true; + return (c == '_'); +} + +} // namespace + +template <class CharT> +bool RegExpParserImpl<CharT>::ParsePropertyClassName(ZoneVector<char>* name_1, + ZoneVector<char>* name_2) { + DCHECK(name_1->empty()); + DCHECK(name_2->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->push_back(static_cast<char>(current())); + } + if (current() == '=') { + for (Advance(); current() != '}'; Advance()) { + if (!IsUnicodePropertyValueCharacter(current())) return false; + if (!has_next()) return false; + name_2->push_back(static_cast<char>(current())); + } + name_2->push_back(0); // null-terminate string. + } + } else { + return false; + } + Advance(); + name_1->push_back(0); // null-terminate string. + + DCHECK(name_1->size() - 1 == std::strlen(name_1->data())); + DCHECK(name_2->empty() || name_2->size() - 1 == std::strlen(name_2->data())); + return true; +} + +template <class CharT> +bool RegExpParserImpl<CharT>::AddPropertyClassRange( + ZoneList<CharacterRange>* add_to_ranges, + CharacterClassStrings* add_to_strings, bool negate, + const ZoneVector<char>& name_1, const ZoneVector<char>& name_2) { + if (name_2.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_ranges, add_to_strings, flags(), + zone())) { + return true; + } + // Interpret "Any", "ASCII", and "Assigned". + if (LookupSpecialPropertyValueName(name, add_to_ranges, negate, flags(), + zone())) { + return true; + } + // 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 + if (negate && IsBinaryPropertyOfStrings(property)) return false; + 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(); + 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_ranges, + add_to_strings, flags(), zone()); + } +} + +#else // V8_INTL_SUPPORT + +template <class CharT> +bool RegExpParserImpl<CharT>::ParsePropertyClassName(ZoneVector<char>* name_1, + ZoneVector<char>* name_2) { + return false; +} + +template <class CharT> +bool RegExpParserImpl<CharT>::AddPropertyClassRange( + ZoneList<CharacterRange>* add_to_ranges, + CharacterClassStrings* add_to_strings, bool negate, + const ZoneVector<char>& name_1, const ZoneVector<char>& name_2) { + return false; +} + +#endif // V8_INTL_SUPPORT + +template <class CharT> +bool RegExpParserImpl<CharT>::ParseUnlimitedLengthHexNumber(int max_value, + base::uc32* value) { + base::uc32 x = 0; + int d = base::HexValue(current()); + if (d < 0) { + return false; + } + while (d >= 0) { + x = x * 16 + d; + if (x > static_cast<base::uc32>(max_value)) { + return false; + } + Advance(); + d = base::HexValue(current()); + } + *value = x; + return true; +} + +// https://tc39.es/ecma262/#prod-CharacterEscape +template <class CharT> +base::uc32 RegExpParserImpl<CharT>::ParseCharacterEscape( + InClassEscapeState in_class_escape_state, + bool* is_escaped_unicode_character) { + DCHECK_EQ('\\', current()); + DCHECK(has_next()); + + Advance(); + + const base::uc32 c = current(); + switch (c) { + // CharacterEscape :: + // 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'; + // CharacterEscape :: + // c ControlLetter + case 'c': { + base::uc32 controlLetter = Next(); + base::uc32 letter = controlLetter & ~('A' ^ 'a'); + if (letter >= 'A' && letter <= 'Z') { + Advance(2); + // Control letters mapped to ASCII control characters in the range + // 0x00-0x1F. + return controlLetter & 0x1F; + } + if (IsUnicodeMode()) { + // With /u and /v, invalid escapes are not treated as identity escapes. + ReportError(RegExpError::kInvalidUnicodeEscape); + return 0; + } + if (in_class_escape_state == InClassEscapeState::kInClass) { + // Inside a character class, we also accept digits and underscore as + // control characters, unless with /u or /v. See Annex B: + // ES#prod-annexB-ClassControlLetter + if ((controlLetter >= '0' && controlLetter <= '9') || + controlLetter == '_') { + Advance(2); + return controlLetter & 0x1F; + } + } + // We match JSC in reading the backslash as a literal + // character instead of as starting an escape. + return '\\'; + } + // CharacterEscape :: + // 0 [lookahead ∉ DecimalDigit] + // [~UnicodeMode] LegacyOctalEscapeSequence + case '0': + // \0 is interpreted as NUL if not followed by another digit. + if (Next() < '0' || Next() > '9') { + Advance(); + return 0; + } + V8_FALLTHROUGH; + 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. + // ES#prod-annexB-LegacyOctalEscapeSequence + if (IsUnicodeMode()) { + // With /u or /v, decimal escape is not interpreted as octal character + // code. + ReportError(RegExpError::kInvalidClassEscape); + return 0; + } + return ParseOctalLiteral(); + // CharacterEscape :: + // HexEscapeSequence + case 'x': { + Advance(); + base::uc32 value; + if (ParseHexEscape(2, &value)) return value; + if (IsUnicodeMode()) { + // With /u or /v, invalid escapes are not treated as identity escapes. + ReportError(RegExpError::kInvalidEscape); + return 0; + } + // If \x is not followed by a two-digit hexadecimal, treat it + // as an identity escape. + return 'x'; + } + // CharacterEscape :: + // RegExpUnicodeEscapeSequence [?UnicodeMode] + case 'u': { + Advance(); + base::uc32 value; + if (ParseUnicodeEscape(&value)) { + *is_escaped_unicode_character = true; + return value; + } + if (IsUnicodeMode()) { + // With /u or /v, invalid escapes are not treated as identity escapes. + ReportError(RegExpError::kInvalidUnicodeEscape); + return 0; + } + // If \u is not followed by a two-digit hexadecimal, treat it + // as an identity escape. + return 'u'; + } + default: + break; + } + + // CharacterEscape :: + // IdentityEscape[?UnicodeMode, ?N] + // + // * With /u, no identity escapes except for syntax characters are + // allowed. + // * With /v, no identity escapes except for syntax characters and + // ClassSetReservedPunctuators (if within a class) are allowed. + // * Without /u or /v: + // * '\c' is not an IdentityEscape. + // * '\k' is not an IdentityEscape when named captures exist. + // * Otherwise, all identity escapes are allowed. + if (unicode_sets() && in_class_escape_state == InClassEscapeState::kInClass) { + if (IsClassSetReservedPunctuator(c)) { + Advance(); + return c; + } + } + if (IsUnicodeMode()) { + if (!IsSyntaxCharacterOrSlash(c)) { + ReportError(RegExpError::kInvalidEscape); + return 0; + } + Advance(); + return c; + } + DCHECK(!IsUnicodeMode()); + if (c == 'c') { + ReportError(RegExpError::kInvalidEscape); + return 0; + } + Advance(); + // Note: It's important to Advance before the HasNamedCaptures call s.t. we + // don't start scanning in the middle of an escape. + if (c == 'k' && HasNamedCaptures(in_class_escape_state)) { + ReportError(RegExpError::kInvalidEscape); + 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 +template <class CharT> +RegExpTree* RegExpParserImpl<CharT>::ParseClassRanges( + ZoneList<CharacterRange>* ranges, bool add_unicode_case_equivalents) { + base::uc32 char_1, char_2; + bool is_class_1, is_class_2; + while (has_more() && current() != ']') { + ParseClassEscape(ranges, zone(), add_unicode_case_equivalents, &char_1, + &is_class_1 CHECK_FAILED); + // ClassAtom + if (current() == '-') { + Advance(); + if (!has_more()) { + // 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), zone()); + ranges->Add(CharacterRange::Singleton('-'), zone()); + break; + } + ParseClassEscape(ranges, zone(), add_unicode_case_equivalents, &char_2, + &is_class_2 CHECK_FAILED); + if (is_class_1 || is_class_2) { + // Either end is an escaped character class. Treat the '-' verbatim. + if (IsUnicodeMode()) { + // ES2015 21.2.2.15.1 step 1. + return ReportError(RegExpError::kInvalidCharacterClass); + } + if (!is_class_1) ranges->Add(CharacterRange::Singleton(char_1), zone()); + ranges->Add(CharacterRange::Singleton('-'), zone()); + if (!is_class_2) ranges->Add(CharacterRange::Singleton(char_2), zone()); + continue; + } + // ES2015 21.2.2.15.1 step 6. + if (char_1 > char_2) { + return ReportError(RegExpError::kOutOfOrderCharacterClass); + } + ranges->Add(CharacterRange::Range(char_1, char_2), zone()); + } else { + if (!is_class_1) ranges->Add(CharacterRange::Singleton(char_1), zone()); + } + } + return nullptr; +} + +// https://tc39.es/ecma262/#prod-ClassEscape +template <class CharT> +void RegExpParserImpl<CharT>::ParseClassEscape( + ZoneList<CharacterRange>* ranges, Zone* zone, + bool add_unicode_case_equivalents, base::uc32* char_out, + bool* is_class_escape) { + *is_class_escape = false; + + if (current() != '\\') { + // Not a ClassEscape. + *char_out = current(); + Advance(); + return; + } + + const base::uc32 next = Next(); + switch (next) { + case 'b': + *char_out = '\b'; + Advance(2); + return; + case '-': + if (IsUnicodeMode()) { + *char_out = next; + Advance(2); + return; + } + break; + case kEndMarker: + ReportError(RegExpError::kEscapeAtEndOfPattern); + return; + default: + break; + } + + static constexpr InClassEscapeState kInClassEscape = + InClassEscapeState::kInClass; + *is_class_escape = + TryParseCharacterClassEscape(next, kInClassEscape, ranges, nullptr, zone, + add_unicode_case_equivalents); + if (*is_class_escape) return; + + bool dummy = false; // Unused. + *char_out = ParseCharacterEscape(kInClassEscape, &dummy); +} + +// https://tc39.es/ecma262/#prod-CharacterClassEscape +template <class CharT> +bool RegExpParserImpl<CharT>::TryParseCharacterClassEscape( + base::uc32 next, InClassEscapeState in_class_escape_state, + ZoneList<CharacterRange>* ranges, CharacterClassStrings* strings, + Zone* zone, bool add_unicode_case_equivalents) { + DCHECK_EQ(current(), '\\'); + DCHECK_EQ(Next(), next); + + switch (next) { + case 'd': + case 'D': + case 's': + case 'S': + case 'w': + case 'W': + CharacterRange::AddClassEscape(static_cast<StandardCharacterSet>(next), + ranges, add_unicode_case_equivalents, + zone); + Advance(2); + return true; + case 'p': + case 'P': { + if (!IsUnicodeMode()) return false; + bool negate = next == 'P'; + Advance(2); + ZoneVector<char> name_1(zone); + ZoneVector<char> name_2(zone); + if (!ParsePropertyClassName(&name_1, &name_2) || + !AddPropertyClassRange(ranges, strings, negate, name_1, name_2)) { + ReportError(in_class_escape_state == InClassEscapeState::kInClass + ? RegExpError::kInvalidClassPropertyName + : RegExpError::kInvalidPropertyName); + } + return true; + } + default: + return false; + } +} + +namespace { + +// Add |string| to |ranges| if length of |string| == 1, otherwise add |string| +// to |strings|. +void AddClassString(ZoneList<base::uc32>* normalized_string, + RegExpTree* regexp_string, ZoneList<CharacterRange>* ranges, + CharacterClassStrings* strings, Zone* zone) { + if (normalized_string->length() == 1) { + ranges->Add(CharacterRange::Singleton(normalized_string->at(0)), zone); + } else { + strings->emplace(normalized_string->ToVector(), regexp_string); + } +} + +} // 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 +template <class CharT> +RegExpTree* RegExpParserImpl<CharT>::ParseClassStringDisjunction( + ZoneList<CharacterRange>* ranges, CharacterClassStrings* strings) { + DCHECK(unicode_sets()); + DCHECK_EQ(current(), '\\'); + DCHECK_EQ(Next(), 'q'); + Advance(2); + if (current() != '{') { + // Identity escape of 'q' is not allowed in unicode mode. + return ReportError(RegExpError::kInvalidEscape); + } + Advance(); + + ZoneList<base::uc32>* string = + zone()->template New<ZoneList<base::uc32>>(4, zone()); + RegExpTextBuilder::SmallRegExpTreeVector string_storage(zone()); + RegExpTextBuilder string_builder(zone(), &string_storage, flags()); + + while (has_more() && current() != '}') { + if (current() == '|') { + AddClassString(string, string_builder.ToRegExp(), ranges, strings, + zone()); + string = zone()->template New<ZoneList<base::uc32>>(4, zone()); + string_storage.clear(); + Advance(); + } else { + base::uc32 c = ParseClassSetCharacter(CHECK_FAILED); + if (ignore_case()) { +#ifdef V8_INTL_SUPPORT + c = u_foldCase(c, U_FOLD_CASE_DEFAULT); +#else + c = AsciiAlphaToLower(c); +#endif + } + string->Add(c, zone()); + string_builder.AddUnicodeCharacter(c); + } + } + + AddClassString(string, string_builder.ToRegExp(), ranges, strings, zone()); + CharacterRange::Canonicalize(ranges); + + // We don't need to handle missing closing '}' here. + // If the character class is correctly closed, ParseClassSetCharacter will + // 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 +// Tree returned based on type_out: +// * kNestedClass: RegExpClassSetExpression +// * For all other types: RegExpClassSetOperand +template <class CharT> +RegExpTree* RegExpParserImpl<CharT>::ParseClassSetOperand( + const RegExpBuilder* builder, ClassSetOperandType* type_out) { + ZoneList<CharacterRange>* ranges = + zone()->template New<ZoneList<CharacterRange>>(1, zone()); + CharacterClassStrings* strings = + zone()->template New<CharacterClassStrings>(zone()); + RegExpTree* tree = + ParseClassSetOperand(builder, type_out, ranges, strings CHECK_FAILED); + DCHECK_IMPLIES(*type_out != ClassSetOperandType::kNestedClass, + tree == nullptr); + DCHECK_IMPLIES(*type_out == ClassSetOperandType::kClassSetCharacter, + ranges->length() == 1); + DCHECK_IMPLIES(*type_out == ClassSetOperandType::kClassSetCharacter, + strings->empty()); + DCHECK_IMPLIES(*type_out == ClassSetOperandType::kNestedClass, + ranges->is_empty()); + DCHECK_IMPLIES(*type_out == ClassSetOperandType::kNestedClass, + strings->empty()); + DCHECK_IMPLIES(*type_out == ClassSetOperandType::kNestedClass, + tree->IsClassSetExpression()); + // ClassSetRange is only used within ClassSetUnion(). + 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) { + tree = zone()->template New<RegExpClassSetOperand>(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. +template <class CharT> +RegExpTree* RegExpParserImpl<CharT>::ParseClassSetOperand( + const RegExpBuilder* builder, ClassSetOperandType* type_out, + ZoneList<CharacterRange>* ranges, CharacterClassStrings* strings) { + DCHECK(unicode_sets()); + base::uc32 c = current(); + if (c == '\\') { + const base::uc32 next = Next(); + if (next == 'q') { + *type_out = ClassSetOperandType::kClassStringDisjunction; + ParseClassStringDisjunction(ranges, strings CHECK_FAILED); + return nullptr; + } + static constexpr InClassEscapeState kInClassEscape = + InClassEscapeState::kInClass; + const bool add_unicode_case_equivalents = ignore_case(); + if (TryParseCharacterClassEscape(next, kInClassEscape, ranges, strings, + zone(), add_unicode_case_equivalents)) { + *type_out = ClassSetOperandType::kCharacterClassEscape; + return nullptr; + } + } + + if (c == '[') { + *type_out = ClassSetOperandType::kNestedClass; + return ParseCharacterClass(builder); + } + + *type_out = ClassSetOperandType::kClassSetCharacter; + c = ParseClassSetCharacter(CHECK_FAILED); + ranges->Add(CharacterRange::Singleton(c), zone()); + return nullptr; +} + +template <class CharT> +base::uc32 RegExpParserImpl<CharT>::ParseClassSetCharacter() { + DCHECK(unicode_sets()); + const base::uc32 c = current(); + if (c == '\\') { + const base::uc32 next = Next(); + switch (next) { + case 'b': + Advance(2); + return '\b'; + case kEndMarker: + ReportError(RegExpError::kEscapeAtEndOfPattern); + return 0; + } + static constexpr InClassEscapeState kInClassEscape = + InClassEscapeState::kInClass; + + bool dummy = false; // Unused. + return ParseCharacterEscape(kInClassEscape, &dummy); + } + if (IsClassSetSyntaxCharacter(c)) { + ReportError(RegExpError::kInvalidCharacterInClass); + return 0; + } + if (IsClassSetReservedDoublePunctuator(c)) { + ReportError(RegExpError::kInvalidClassSetOperation); + return 0; + } + Advance(); + return c; +} + +namespace { + +bool MayContainStrings(ClassSetOperandType type, RegExpTree* operand) { + switch (type) { + case ClassSetOperandType::kClassSetCharacter: + case ClassSetOperandType::kClassSetRange: + return false; + case ClassSetOperandType::kCharacterClassEscape: + case ClassSetOperandType::kClassStringDisjunction: + return operand->AsClassSetOperand()->has_strings(); + case ClassSetOperandType::kNestedClass: + if (operand->IsClassRanges()) return false; + return operand->AsClassSetExpression()->may_contain_strings(); + } +} + +} // 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 <class CharT> +RegExpTree* RegExpParserImpl<CharT>::ParseClassUnion( + const RegExpBuilder* builder, bool is_negated, RegExpTree* first_operand, + ClassSetOperandType first_operand_type, ZoneList<CharacterRange>* ranges, + CharacterClassStrings* strings) { + DCHECK(unicode_sets()); + ZoneList<RegExpTree*>* operands = + zone()->template New<ZoneList<RegExpTree*>>(2, zone()); + bool may_contain_strings = false; + // Add the lhs to operands if necessary. + // Either the lhs values were added to |ranges|/|strings| (in which case + // |first_operand| is nullptr), or the lhs was evaluated to a tree and passed + // as |first_operand| (in which case |ranges| and |strings| are empty). + 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); + } + Advance(); + if (!has_more()) { + // If we reach the end we break out of the loop and let the + // following code report an error. + break; + } + // 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 + if (last_type != ClassSetOperandType::kClassSetCharacter) { + return ReportError(RegExpError::kInvalidCharacterClass); + } + ParseClassSetOperand(builder, &last_type, ranges, strings 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) { + return ReportError(RegExpError::kOutOfOrderCharacterClass); + } + ranges->Add(CharacterRange::Range(from, to), zone()); + last_type = ClassSetOperandType::kClassSetRange; + } else { + DCHECK_NE(current(), '-'); + RegExpTree* operand = ParseClassSetOperand(builder, &last_type, ranges, + strings 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<RegExpClassSetOperand>(ranges, strings), + zone()); + ranges = zone()->template New<ZoneList<CharacterRange>>(2, zone()); + strings = zone()->template New<CharacterClassStrings>(zone()); + } + operands->Add(operand, zone()); + } + } + } + + if (!has_more()) { + return ReportError(RegExpError::kUnterminatedCharacterClass); + } + + // 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<RegExpClassSetOperand>(ranges, strings), + zone()); + } + + DCHECK_EQ(current(), ']'); + Advance(); + + if (is_negated && may_contain_strings) { + return ReportError(RegExpError::kNegatedCharacterClassWithStrings); + } + + if (operands->is_empty()) { + // Return empty expression if no operands were added (e.g. [\P{Any}] + // produces an empty range). + DCHECK(ranges->is_empty()); + DCHECK(strings->empty()); + return RegExpClassSetExpression::Empty(zone(), is_negated); + } + + return zone()->template New<RegExpClassSetExpression>( + 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 +template <class CharT> +RegExpTree* RegExpParserImpl<CharT>::ParseClassIntersection( + const RegExpBuilder* builder, bool is_negated, RegExpTree* first_operand, + ClassSetOperandType first_operand_type) { + DCHECK(unicode_sets()); + DCHECK(current() == '&' && Next() == '&'); + bool may_contain_strings = + MayContainStrings(first_operand_type, first_operand); + ZoneList<RegExpTree*>* operands = + zone()->template New<ZoneList<RegExpTree*>>(2, zone()); + operands->Add(first_operand, zone()); + while (has_more() && current() != ']') { + if (current() != '&' || Next() != '&') { + return ReportError(RegExpError::kInvalidClassSetOperation); + } + Advance(2); + // [lookahead ≠&] + if (current() == '&') { + return ReportError(RegExpError::kInvalidCharacterInClass); + } + + ClassSetOperandType operand_type; + RegExpTree* operand = + ParseClassSetOperand(builder, &operand_type CHECK_FAILED); + may_contain_strings &= MayContainStrings(operand_type, operand); + operands->Add(operand, zone()); + } + if (!has_more()) { + return ReportError(RegExpError::kUnterminatedCharacterClass); + } + if (is_negated && may_contain_strings) { + return ReportError(RegExpError::kNegatedCharacterClassWithStrings); + } + DCHECK_EQ(current(), ']'); + Advance(); + return zone()->template New<RegExpClassSetExpression>( + 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 +template <class CharT> +RegExpTree* RegExpParserImpl<CharT>::ParseClassSubtraction( + const RegExpBuilder* builder, bool is_negated, RegExpTree* first_operand, + ClassSetOperandType first_operand_type) { + DCHECK(unicode_sets()); + DCHECK(current() == '-' && Next() == '-'); + const bool may_contain_strings = + MayContainStrings(first_operand_type, first_operand); + if (is_negated && may_contain_strings) { + return ReportError(RegExpError::kNegatedCharacterClassWithStrings); + } + ZoneList<RegExpTree*>* operands = + zone()->template New<ZoneList<RegExpTree*>>(2, zone()); + operands->Add(first_operand, zone()); + while (has_more() && current() != ']') { + if (current() != '-' || Next() != '-') { + return ReportError(RegExpError::kInvalidClassSetOperation); + } + Advance(2); + ClassSetOperandType dummy; // unused + RegExpTree* operand = ParseClassSetOperand(builder, &dummy CHECK_FAILED); + operands->Add(operand, zone()); + } + if (!has_more()) { + return ReportError(RegExpError::kUnterminatedCharacterClass); + } + DCHECK_EQ(current(), ']'); + Advance(); + return zone()->template New<RegExpClassSetExpression>( + RegExpClassSetExpression::OperationType::kSubtraction, is_negated, + may_contain_strings, operands); +} + +// https://tc39.es/ecma262/#prod-CharacterClass +template <class CharT> +RegExpTree* RegExpParserImpl<CharT>::ParseCharacterClass( + const RegExpBuilder* builder) { + DCHECK_EQ(current(), '['); + Advance(); + bool is_negated = false; + if (current() == '^') { + is_negated = true; + Advance(); + } + ZoneList<CharacterRange>* ranges = + zone()->template New<ZoneList<CharacterRange>>(2, zone()); + if (current() == ']') { + Advance(); + if (unicode_sets()) { + return RegExpClassSetExpression::Empty(zone(), is_negated); + } else { + RegExpClassRanges::ClassRangesFlags class_ranges_flags; + if (is_negated) class_ranges_flags = RegExpClassRanges::NEGATED; + return zone()->template New<RegExpClassRanges>(zone(), ranges, + class_ranges_flags); + } + } + + if (!unicode_sets()) { + bool add_unicode_case_equivalents = IsUnicodeMode() && ignore_case(); + ParseClassRanges(ranges, add_unicode_case_equivalents CHECK_FAILED); + if (!has_more()) { + return ReportError(RegExpError::kUnterminatedCharacterClass); + } + DCHECK_EQ(current(), ']'); + Advance(); + RegExpClassRanges::ClassRangesFlags character_class_flags; + if (is_negated) character_class_flags = RegExpClassRanges::NEGATED; + return zone()->template New<RegExpClassRanges>(zone(), ranges, + character_class_flags); + } else { + ClassSetOperandType operand_type; + CharacterClassStrings* strings = + zone()->template New<CharacterClassStrings>(zone()); + RegExpTree* operand = ParseClassSetOperand(builder, &operand_type, ranges, + strings CHECK_FAILED); + switch (current()) { + case '-': + if (Next() == '-') { + if (operand == nullptr) { + operand = + zone()->template New<RegExpClassSetOperand>(ranges, strings); + } + return ParseClassSubtraction(builder, is_negated, operand, + operand_type); + } + // ClassSetRange is handled in ParseClassUnion(). + break; + case '&': + if (Next() == '&') { + if (operand == nullptr) { + operand = + zone()->template New<RegExpClassSetOperand>(ranges, strings); + } + return ParseClassIntersection(builder, is_negated, operand, + operand_type); + } + } + return ParseClassUnion(builder, is_negated, operand, operand_type, ranges, + strings); + } +} + +#undef CHECK_FAILED + +template <class CharT> +bool RegExpParserImpl<CharT>::Parse(RegExpCompileData* result) { + DCHECK_NOT_NULL(result); + RegExpTree* tree = ParsePattern(); + + if (failed()) { + DCHECK_NULL(tree); + DCHECK_NE(error_, RegExpError::kNone); + result->error = error_; + result->error_pos = error_pos_; + return false; + } + + DCHECK_NOT_NULL(tree); + DCHECK_EQ(error_, RegExpError::kNone); + if (v8_flags.trace_regexp_parser) { + StdoutStream os; + tree->Print(os, zone()); + os << "\n"; + } + + result->tree = tree; + const int capture_count = captures_started(); + result->simple = tree->IsAtom() && simple() && capture_count == 0; + result->contains_anchor = contains_anchor(); + result->capture_count = capture_count; + result->named_captures = GetNamedCaptures(); + return true; +} + +void RegExpBuilder::FlushText() { text_builder().FlushText(); } + +void RegExpBuilder::AddCharacter(base::uc16 c) { + pending_empty_ = false; + text_builder().AddCharacter(c); +} + +void RegExpBuilder::AddUnicodeCharacter(base::uc32 c) { + pending_empty_ = false; + text_builder().AddUnicodeCharacter(c); +} + +void RegExpBuilder::AddEscapedUnicodeCharacter(base::uc32 character) { + pending_empty_ = false; + text_builder().AddEscapedUnicodeCharacter(character); +} + +void RegExpBuilder::AddEmpty() { + text_builder().FlushPendingSurrogate(); + pending_empty_ = true; +} + +void RegExpBuilder::AddClassRanges(RegExpClassRanges* cc) { + pending_empty_ = false; + text_builder().AddClassRanges(cc); +} + +void RegExpBuilder::AddAtom(RegExpTree* term) { + if (term->IsEmpty()) { + AddEmpty(); + return; + } + pending_empty_ = false; + if (term->IsTextElement()) { + text_builder().AddAtom(term); + } else { + FlushText(); + terms_.emplace_back(term); + } +} + +void RegExpBuilder::AddTerm(RegExpTree* term) { + DCHECK(!term->IsEmpty()); + pending_empty_ = false; + if (term->IsTextElement()) { + text_builder().AddTerm(term); + } else { + FlushText(); + terms_.emplace_back(term); + } +} + +void RegExpBuilder::AddAssertion(RegExpTree* assert) { + FlushText(); + pending_empty_ = false; + terms_.emplace_back(assert); +} + +void RegExpBuilder::NewAlternative() { FlushTerms(); } + +void RegExpBuilder::FlushTerms() { + FlushText(); + size_t num_terms = terms_.size(); + RegExpTree* alternative; + if (num_terms == 0) { + alternative = zone()->New<RegExpEmpty>(); + } else if (num_terms == 1) { + alternative = terms_.back(); + } else { + alternative = + zone()->New<RegExpAlternative>(zone()->New<ZoneList<RegExpTree*>>( + base::VectorOf(terms_.begin(), terms_.size()), zone())); + } + alternatives_.emplace_back(alternative); + terms_.clear(); +} + +RegExpTree* RegExpBuilder::ToRegExp() { + FlushTerms(); + size_t num_alternatives = alternatives_.size(); + if (num_alternatives == 0) return zone()->New<RegExpEmpty>(); + if (num_alternatives == 1) return alternatives_.back(); + return zone()->New<RegExpDisjunction>(zone()->New<ZoneList<RegExpTree*>>( + base::VectorOf(alternatives_.begin(), alternatives_.size()), zone())); +} + +bool RegExpBuilder::AddQuantifierToAtom( + int min, int max, RegExpQuantifier::QuantifierType quantifier_type) { + if (pending_empty_) { + pending_empty_ = false; + return true; + } + RegExpTree* atom = text_builder().PopLastAtom(); + if (atom != nullptr) { + FlushText(); + } else if (terms_.size() > 0) { + atom = terms_.back(); + terms_.pop_back(); + if (atom->IsLookaround()) { + // With /u or /v, lookarounds are not quantifiable. + if (IsUnicodeMode()) return false; + // Lookbehinds are not quantifiable. + if (atom->AsLookaround()->type() == RegExpLookaround::LOOKBEHIND) { + return false; + } + } + if (atom->max_match() == 0) { + // Guaranteed to only match an empty string. + if (min == 0) { + return true; + } + terms_.emplace_back(atom); + return true; + } + } else { + // Only call immediately after adding an atom or character! + UNREACHABLE(); + } + terms_.emplace_back( + zone()->New<RegExpQuantifier>(min, max, quantifier_type, atom)); + return true; +} + +template class RegExpParserImpl<uint8_t>; +template class RegExpParserImpl<base::uc16>; + +} // namespace + +// static +bool RegExpParser::ParseRegExpFromHeapString(Isolate* isolate, Zone* zone, + Handle<String> input, + RegExpFlags flags, + RegExpCompileData* result) { + DisallowGarbageCollection no_gc; + uintptr_t stack_limit = isolate->stack_guard()->real_climit(); + String::FlatContent content = input->GetFlatContent(no_gc); + if (content.IsOneByte()) { + base::Vector<const uint8_t> v = content.ToOneByteVector(); + return RegExpParserImpl<uint8_t>{v.begin(), v.length(), flags, + stack_limit, zone, no_gc} + .Parse(result); + } else { + base::Vector<const base::uc16> v = content.ToUC16Vector(); + return RegExpParserImpl<base::uc16>{v.begin(), v.length(), flags, + stack_limit, zone, no_gc} + .Parse(result); + } +} + +// static +template <class CharT> +bool RegExpParser::VerifyRegExpSyntax(Zone* zone, uintptr_t stack_limit, + const CharT* input, int input_length, + RegExpFlags flags, + RegExpCompileData* result, + const DisallowGarbageCollection& no_gc) { + return RegExpParserImpl<CharT>{input, input_length, flags, + stack_limit, zone, no_gc} + .Parse(result); +} + +template bool RegExpParser::VerifyRegExpSyntax<uint8_t>( + Zone*, uintptr_t, const uint8_t*, int, RegExpFlags, RegExpCompileData*, + const DisallowGarbageCollection&); +template bool RegExpParser::VerifyRegExpSyntax<base::uc16>( + Zone*, uintptr_t, const base::uc16*, int, RegExpFlags, RegExpCompileData*, + const DisallowGarbageCollection&); + +} // namespace internal +} // namespace v8 diff --git a/js/src/irregexp/imported/regexp-parser.h b/js/src/irregexp/imported/regexp-parser.h new file mode 100644 index 0000000000..1e45d97532 --- /dev/null +++ b/js/src/irregexp/imported/regexp-parser.h @@ -0,0 +1,34 @@ +// Copyright 2016 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. + +#ifndef V8_REGEXP_REGEXP_PARSER_H_ +#define V8_REGEXP_REGEXP_PARSER_H_ + +#include "irregexp/RegExpShim.h" + +namespace v8 { +namespace internal { + +class String; +class Zone; + +struct RegExpCompileData; + +class V8_EXPORT_PRIVATE RegExpParser : public AllStatic { + public: + static bool ParseRegExpFromHeapString(Isolate* isolate, Zone* zone, + Handle<String> input, RegExpFlags flags, + RegExpCompileData* result); + + template <class CharT> + static bool VerifyRegExpSyntax(Zone* zone, uintptr_t stack_limit, + const CharT* input, int input_length, + RegExpFlags flags, RegExpCompileData* result, + const DisallowGarbageCollection& no_gc); +}; + +} // namespace internal +} // namespace v8 + +#endif // V8_REGEXP_REGEXP_PARSER_H_ diff --git a/js/src/irregexp/imported/regexp-stack.cc b/js/src/irregexp/imported/regexp-stack.cc new file mode 100644 index 0000000000..892ba3a069 --- /dev/null +++ b/js/src/irregexp/imported/regexp-stack.cc @@ -0,0 +1,96 @@ +// Copyright 2009 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-stack.h" + + +namespace v8 { +namespace internal { + +RegExpStackScope::RegExpStackScope(Isolate* isolate) + : regexp_stack_(isolate->regexp_stack()), + old_sp_top_delta_(regexp_stack_->sp_top_delta()) { + DCHECK(regexp_stack_->IsValid()); +} + +RegExpStackScope::~RegExpStackScope() { + CHECK_EQ(old_sp_top_delta_, regexp_stack_->sp_top_delta()); + regexp_stack_->ResetIfEmpty(); +} + +RegExpStack::RegExpStack() : thread_local_(this) {} + +RegExpStack::~RegExpStack() { thread_local_.FreeAndInvalidate(); } + +char* RegExpStack::ArchiveStack(char* to) { + if (!thread_local_.owns_memory_) { + // Force dynamic stacks prior to archiving. Any growth will do. A dynamic + // stack is needed because stack archival & restoration rely on `memory_` + // pointing at a fixed-location backing store, whereas the static stack is + // tied to a RegExpStack instance. + EnsureCapacity(thread_local_.memory_size_ + 1); + DCHECK(thread_local_.owns_memory_); + } + + MemCopy(reinterpret_cast<void*>(to), &thread_local_, kThreadLocalSize); + thread_local_ = ThreadLocal(this); + return to + kThreadLocalSize; +} + + +char* RegExpStack::RestoreStack(char* from) { + MemCopy(&thread_local_, reinterpret_cast<void*>(from), kThreadLocalSize); + return from + kThreadLocalSize; +} + +void RegExpStack::ThreadLocal::ResetToStaticStack(RegExpStack* regexp_stack) { + if (owns_memory_) DeleteArray(memory_); + + memory_ = regexp_stack->static_stack_; + memory_top_ = regexp_stack->static_stack_ + kStaticStackSize; + memory_size_ = kStaticStackSize; + stack_pointer_ = memory_top_; + limit_ = reinterpret_cast<Address>(regexp_stack->static_stack_) + + kStackLimitSlack * kSystemPointerSize; + owns_memory_ = false; +} + +void RegExpStack::ThreadLocal::FreeAndInvalidate() { + if (owns_memory_) DeleteArray(memory_); + + // This stack may not be used after being freed. Just reset to invalid values + // to ensure we don't accidentally use old memory areas. + memory_ = nullptr; + memory_top_ = nullptr; + memory_size_ = 0; + stack_pointer_ = nullptr; + limit_ = kMemoryTop; +} + +Address RegExpStack::EnsureCapacity(size_t size) { + if (size > kMaximumStackSize) return kNullAddress; + if (thread_local_.memory_size_ < size) { + if (size < kMinimumDynamicStackSize) size = kMinimumDynamicStackSize; + uint8_t* new_memory = NewArray<uint8_t>(size); + if (thread_local_.memory_size_ > 0) { + // Copy original memory into top of new memory. + MemCopy(new_memory + size - thread_local_.memory_size_, + thread_local_.memory_, thread_local_.memory_size_); + if (thread_local_.owns_memory_) DeleteArray(thread_local_.memory_); + } + ptrdiff_t delta = sp_top_delta(); + thread_local_.memory_ = new_memory; + thread_local_.memory_top_ = new_memory + size; + thread_local_.memory_size_ = size; + thread_local_.stack_pointer_ = thread_local_.memory_top_ + delta; + thread_local_.limit_ = reinterpret_cast<Address>(new_memory) + + kStackLimitSlack * kSystemPointerSize; + thread_local_.owns_memory_ = true; + } + return reinterpret_cast<Address>(thread_local_.memory_top_); +} + + +} // namespace internal +} // namespace v8 diff --git a/js/src/irregexp/imported/regexp-stack.h b/js/src/irregexp/imported/regexp-stack.h new file mode 100644 index 0000000000..fc8827116b --- /dev/null +++ b/js/src/irregexp/imported/regexp-stack.h @@ -0,0 +1,159 @@ +// Copyright 2009 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. + +#ifndef V8_REGEXP_REGEXP_STACK_H_ +#define V8_REGEXP_REGEXP_STACK_H_ + +#include "irregexp/RegExpShim.h" + +namespace v8 { +namespace internal { + +class RegExpStack; + +// Maintains a per-v8thread stack area that can be used by irregexp +// implementation for its backtracking stack. +class V8_NODISCARD RegExpStackScope final { + public: + // Create and delete an instance to control the life-time of a growing stack. + + // Initializes the stack memory area if necessary. + explicit RegExpStackScope(Isolate* isolate); + ~RegExpStackScope(); // Releases the stack if it has grown. + RegExpStackScope(const RegExpStackScope&) = delete; + RegExpStackScope& operator=(const RegExpStackScope&) = delete; + + RegExpStack* stack() const { return regexp_stack_; } + + private: + RegExpStack* const regexp_stack_; + const ptrdiff_t old_sp_top_delta_; +}; + +class RegExpStack final { + public: + RegExpStack(); + ~RegExpStack(); + RegExpStack(const RegExpStack&) = delete; + RegExpStack& operator=(const RegExpStack&) = delete; + + // Number of allocated locations on the stack below the limit. No sequence of + // pushes must be longer than this without doing a stack-limit check. + static constexpr int kStackLimitSlack = 32; + + Address memory_top() const { + DCHECK_NE(0, thread_local_.memory_size_); + DCHECK_EQ(thread_local_.memory_top_, + thread_local_.memory_ + thread_local_.memory_size_); + return reinterpret_cast<Address>(thread_local_.memory_top_); + } + + Address stack_pointer() const { + return reinterpret_cast<Address>(thread_local_.stack_pointer_); + } + + size_t memory_size() const { return thread_local_.memory_size_; } + + // If the stack pointer gets below the limit, we should react and + // either grow the stack or report an out-of-stack exception. + // There is only a limited number of locations below the stack limit, + // so users of the stack should check the stack limit during any + // sequence of pushes longer that this. + Address* limit_address_address() { return &thread_local_.limit_; } + + // Ensures that there is a memory area with at least the specified size. + // If passing zero, the default/minimum size buffer is allocated. + Address EnsureCapacity(size_t size); + + // Thread local archiving. + static constexpr int ArchiveSpacePerThread() { + return static_cast<int>(kThreadLocalSize); + } + char* ArchiveStack(char* to); + char* RestoreStack(char* from); + void FreeThreadResources() { thread_local_.ResetToStaticStack(this); } + + // Maximal size of allocated stack area. + static constexpr size_t kMaximumStackSize = 64 * MB; + + private: + // Artificial limit used when the thread-local state has been destroyed. + static const Address kMemoryTop = + static_cast<Address>(static_cast<uintptr_t>(-1)); + + // Minimal size of dynamically-allocated stack area. + static constexpr size_t kMinimumDynamicStackSize = 1 * KB; + + // In addition to dynamically-allocated, variable-sized stacks, we also have + // a statically allocated and sized area that is used whenever no dynamic + // stack is allocated. This guarantees that a stack is always available and + // we can skip availability-checks later on. + // It's double the slack size to ensure that we have a bit of breathing room + // before NativeRegExpMacroAssembler::GrowStack must be called. + static constexpr size_t kStaticStackSize = + 2 * kStackLimitSlack * kSystemPointerSize; + uint8_t static_stack_[kStaticStackSize] = {0}; + + static_assert(kStaticStackSize <= kMaximumStackSize); + + // Structure holding the allocated memory, size and limit. Thread switching + // archives and restores this struct. + struct ThreadLocal { + explicit ThreadLocal(RegExpStack* regexp_stack) { + ResetToStaticStack(regexp_stack); + } + + // If memory_size_ > 0 then + // - memory_, memory_top_, stack_pointer_ must be non-nullptr + // - memory_top_ = memory_ + memory_size_ + // - memory_ <= stack_pointer_ <= memory_top_ + uint8_t* memory_ = nullptr; + uint8_t* memory_top_ = nullptr; + size_t memory_size_ = 0; + uint8_t* stack_pointer_ = nullptr; + Address limit_ = kNullAddress; + bool owns_memory_ = false; // Whether memory_ is owned and must be freed. + + void ResetToStaticStack(RegExpStack* regexp_stack); + void ResetToStaticStackIfEmpty(RegExpStack* regexp_stack) { + if (stack_pointer_ == memory_top_) ResetToStaticStack(regexp_stack); + } + void FreeAndInvalidate(); + }; + static constexpr size_t kThreadLocalSize = sizeof(ThreadLocal); + + Address memory_top_address_address() { + return reinterpret_cast<Address>(&thread_local_.memory_top_); + } + + Address stack_pointer_address() { + return reinterpret_cast<Address>(&thread_local_.stack_pointer_); + } + + // A position-independent representation of the stack pointer. + ptrdiff_t sp_top_delta() const { + ptrdiff_t result = + reinterpret_cast<intptr_t>(thread_local_.stack_pointer_) - + reinterpret_cast<intptr_t>(thread_local_.memory_top_); + DCHECK_LE(result, 0); + return result; + } + + // Resets the buffer if it has grown beyond the default/minimum size and is + // empty. + void ResetIfEmpty() { thread_local_.ResetToStaticStackIfEmpty(this); } + + // Whether the ThreadLocal storage has been invalidated. + bool IsValid() const { return thread_local_.memory_ != nullptr; } + + ThreadLocal thread_local_; + + friend class ExternalReference; + friend class RegExpStackScope; +}; + +} // namespace internal +} // namespace v8 + +#endif // V8_REGEXP_REGEXP_STACK_H_ diff --git a/js/src/irregexp/imported/regexp.h b/js/src/irregexp/imported/regexp.h new file mode 100644 index 0000000000..50269a4b71 --- /dev/null +++ b/js/src/irregexp/imported/regexp.h @@ -0,0 +1,236 @@ +// Copyright 2012 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. + +#ifndef V8_REGEXP_REGEXP_H_ +#define V8_REGEXP_REGEXP_H_ + +#include "irregexp/imported/regexp-error.h" +#include "irregexp/RegExpShim.h" + +namespace v8 { +namespace internal { + +class JSRegExp; +class RegExpCapture; +class RegExpMatchInfo; +class RegExpNode; +class RegExpTree; + +enum class RegExpCompilationTarget : int { kBytecode, kNative }; + +// TODO(jgruber): Do not expose in regexp.h. +// TODO(jgruber): Consider splitting between ParseData and CompileData. +struct RegExpCompileData { + // The parsed AST as produced by the RegExpParser. + RegExpTree* tree = nullptr; + + // The compiled Node graph as produced by RegExpTree::ToNode methods. + RegExpNode* node = nullptr; + + // Either the generated code as produced by the compiler or a trampoline + // to the interpreter. + Handle<Object> code; + + // True, iff the pattern is a 'simple' atom with zero captures. In other + // words, the pattern consists of a string with no metacharacters and special + // regexp features, and can be implemented as a standard string search. + bool simple = true; + + // True, iff the pattern is anchored at the start of the string with '^'. + bool contains_anchor = false; + + // Only set if the pattern contains named captures. + // Note: the lifetime equals that of the parse/compile zone. + ZoneVector<RegExpCapture*>* named_captures = nullptr; + + // The error message. Only used if an error occurred during parsing or + // compilation. + RegExpError error = RegExpError::kNone; + + // The position at which the error was detected. Only used if an + // error occurred. + int error_pos = 0; + + // The number of capture groups, without the global capture \0. + int capture_count = 0; + + // The number of registers used by the generated code. + int register_count = 0; + + // The compilation target (bytecode or native code). + RegExpCompilationTarget compilation_target; +}; + +class RegExp final : public AllStatic { + public: + // Whether the irregexp engine generates interpreter bytecode. + static bool CanGenerateBytecode(); + + // Verify that the given flags combination is valid. + V8_EXPORT_PRIVATE static bool VerifyFlags(RegExpFlags flags); + + // Verify the given pattern, i.e. check that parsing succeeds. If + // verification fails, `regexp_error_out` is set. + template <class CharT> + static bool VerifySyntax(Zone* zone, uintptr_t stack_limit, + const CharT* input, int input_length, + RegExpFlags flags, RegExpError* regexp_error_out, + const DisallowGarbageCollection& no_gc); + + // Parses the RegExp pattern and prepares the JSRegExp object with + // generic data and choice of implementation - as well as what + // the implementation wants to store in the data field. + // Returns false if compilation fails. + V8_WARN_UNUSED_RESULT static MaybeHandle<Object> Compile( + Isolate* isolate, Handle<JSRegExp> re, Handle<String> pattern, + RegExpFlags flags, uint32_t backtrack_limit); + + // Ensures that a regexp is fully compiled and ready to be executed on a + // subject string. Returns true on success. Return false on failure, and + // then an exception will be pending. + V8_WARN_UNUSED_RESULT static bool EnsureFullyCompiled(Isolate* isolate, + Handle<JSRegExp> re, + Handle<String> subject); + + enum CallOrigin : int { + kFromRuntime = 0, + kFromJs = 1, + }; + + enum class ExecQuirks { + kNone, + // Used to work around an issue in the RegExpPrototypeSplit fast path, + // which diverges from the spec by not creating a sticky copy of the RegExp + // instance and calling `exec` in a loop. If called in this context, we + // must not update the last_match_info on a successful match at the subject + // string end. See crbug.com/1075514 for more information. + kTreatMatchAtEndAsFailure, + }; + + // See ECMA-262 section 15.10.6.2. + // This function calls the garbage collector if necessary. + V8_EXPORT_PRIVATE V8_WARN_UNUSED_RESULT static MaybeHandle<Object> Exec( + Isolate* isolate, Handle<JSRegExp> regexp, Handle<String> subject, + int index, Handle<RegExpMatchInfo> last_match_info, + ExecQuirks exec_quirks = ExecQuirks::kNone); + + V8_EXPORT_PRIVATE V8_WARN_UNUSED_RESULT static MaybeHandle<Object> + ExperimentalOneshotExec(Isolate* isolate, Handle<JSRegExp> regexp, + Handle<String> subject, int index, + Handle<RegExpMatchInfo> last_match_info, + ExecQuirks exec_quirks = ExecQuirks::kNone); + + // Integral return values used throughout regexp code layers. + static constexpr int kInternalRegExpFailure = 0; + static constexpr int kInternalRegExpSuccess = 1; + static constexpr int kInternalRegExpException = -1; + static constexpr int kInternalRegExpRetry = -2; + static constexpr int kInternalRegExpFallbackToExperimental = -3; + static constexpr int kInternalRegExpSmallestResult = -3; + + enum IrregexpResult : int32_t { + RE_FAILURE = kInternalRegExpFailure, + RE_SUCCESS = kInternalRegExpSuccess, + RE_EXCEPTION = kInternalRegExpException, + RE_RETRY = kInternalRegExpRetry, + RE_FALLBACK_TO_EXPERIMENTAL = kInternalRegExpFallbackToExperimental, + }; + + // Set last match info. If match is nullptr, then setting captures is + // omitted. + static Handle<RegExpMatchInfo> SetLastMatchInfo( + Isolate* isolate, Handle<RegExpMatchInfo> last_match_info, + Handle<String> subject, int capture_count, int32_t* match); + + V8_EXPORT_PRIVATE static bool CompileForTesting( + Isolate* isolate, Zone* zone, RegExpCompileData* input, RegExpFlags flags, + Handle<String> pattern, Handle<String> sample_subject, bool is_one_byte); + + V8_EXPORT_PRIVATE static void DotPrintForTesting(const char* label, + RegExpNode* node); + + static const int kRegExpTooLargeToOptimize = 20 * KB; + + V8_WARN_UNUSED_RESULT + static MaybeHandle<Object> ThrowRegExpException(Isolate* isolate, + Handle<JSRegExp> re, + RegExpFlags flags, + Handle<String> pattern, + RegExpError error); + static void ThrowRegExpException(Isolate* isolate, Handle<JSRegExp> re, + RegExpError error_text); + + static bool IsUnmodifiedRegExp(Isolate* isolate, Handle<JSRegExp> regexp); + + static Handle<FixedArray> CreateCaptureNameMap( + Isolate* isolate, ZoneVector<RegExpCapture*>* named_captures); +}; + +// Uses a special global mode of irregexp-generated code to perform a global +// search and return multiple results at once. As such, this is essentially an +// iterator over multiple results (retrieved batch-wise in advance). +class RegExpGlobalCache final { + public: + RegExpGlobalCache(Handle<JSRegExp> regexp, Handle<String> subject, + Isolate* isolate); + + ~RegExpGlobalCache(); + + // Fetch the next entry in the cache for global regexp match results. + // This does not set the last match info. Upon failure, nullptr is + // returned. The cause can be checked with Result(). The previous result is + // still in available in memory when a failure happens. + int32_t* FetchNext(); + + int32_t* LastSuccessfulMatch(); + + bool HasException() { return num_matches_ < 0; } + + private: + int AdvanceZeroLength(int last_index); + + int num_matches_; + int max_matches_; + int current_match_index_; + int registers_per_match_; + // Pointer to the last set of captures. + int32_t* register_array_; + int register_array_size_; + Handle<JSRegExp> regexp_; + Handle<String> subject_; + Isolate* isolate_; +}; + +// Caches results for specific regexp queries on the isolate. At the time of +// writing, this is used during global calls to RegExp.prototype.exec and +// @@split. +class RegExpResultsCache final : public AllStatic { + 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); + // 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<String> key_string, + Handle<Object> key_pattern, Handle<FixedArray> value_array, + Handle<FixedArray> last_match_cache, ResultsCacheType type); + static void Clear(FixedArray cache); + + static constexpr int kRegExpResultsCacheSize = 0x100; + + private: + static constexpr int kStringOffset = 0; + static constexpr int kPatternOffset = 1; + static constexpr int kArrayOffset = 2; + static constexpr int kLastMatchOffset = 3; + static constexpr int kArrayEntriesPerCacheEntry = 4; +}; + +} // namespace internal +} // namespace v8 + +#endif // V8_REGEXP_REGEXP_H_ diff --git a/js/src/irregexp/imported/special-case.cc b/js/src/irregexp/imported/special-case.cc new file mode 100644 index 0000000000..f5a9928b3a --- /dev/null +++ b/js/src/irregexp/imported/special-case.cc @@ -0,0 +1,111 @@ +// Copyright 2020 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. + +// Automatically generated by regexp/gen-regexp-special-case.cc + +// The following functions are used to build UnicodeSets +// for special cases where the case-folding algorithm used by +// UnicodeSet::closeOver(USET_CASE_INSENSITIVE) does not match +// the algorithm defined in ECMAScript 2020 21.2.2.8.2 (Runtime +// Semantics: Canonicalize) step 3. + +#ifdef V8_INTL_SUPPORT +#include "irregexp/imported/special-case.h" + +#include "unicode/uniset.h" +namespace v8 { +namespace internal { + +icu::UnicodeSet BuildIgnoreSet() { + icu::UnicodeSet set; + set.add(0xdf); + set.add(0x17f); + set.add(0x390); + set.add(0x3b0); + set.add(0x3f4); + set.add(0x1e9e); + set.add(0x1f80, 0x1faf); + set.add(0x1fb3); + set.add(0x1fbc); + set.add(0x1fc3); + set.add(0x1fcc); + set.add(0x1fd3); + set.add(0x1fe3); + set.add(0x1ff3); + set.add(0x1ffc); + set.add(0x2126); + set.add(0x212a, 0x212b); + set.add(0xfb05, 0xfb06); + set.freeze(); + return set; +} + +struct IgnoreSetData { + IgnoreSetData() : set(BuildIgnoreSet()) {} + const icu::UnicodeSet set; +}; + +//static +const icu::UnicodeSet& RegExpCaseFolding::IgnoreSet() { + static base::LazyInstance<IgnoreSetData>::type set = + LAZY_INSTANCE_INITIALIZER; + return set.Pointer()->set; +} + +icu::UnicodeSet BuildSpecialAddSet() { + icu::UnicodeSet set; + set.add(0x4b); + set.add(0x53); + set.add(0x6b); + set.add(0x73); + set.add(0xc5); + set.add(0xe5); + set.add(0x398); + set.add(0x3a9); + set.add(0x3b8); + set.add(0x3c9); + set.add(0x3d1); + set.freeze(); + return set; +} + +struct SpecialAddSetData { + SpecialAddSetData() : set(BuildSpecialAddSet()) {} + const icu::UnicodeSet set; +}; + +//static +const icu::UnicodeSet& RegExpCaseFolding::SpecialAddSet() { + static base::LazyInstance<SpecialAddSetData>::type set = + LAZY_INSTANCE_INITIALIZER; + return set.Pointer()->set; +} + +icu::UnicodeSet BuildUnicodeNonSimpleCloseOverSet() { + icu::UnicodeSet set; + set.add(0x390); + set.add(0x3b0); + set.add(0x1fd3); + set.add(0x1fe3); + set.add(0xfb05, 0xfb06); + set.freeze(); + return set; +} + +struct UnicodeNonSimpleCloseOverSetData { + UnicodeNonSimpleCloseOverSetData() : set(BuildUnicodeNonSimpleCloseOverSet()) {} + const icu::UnicodeSet set; +}; + +//static +const icu::UnicodeSet& RegExpCaseFolding::UnicodeNonSimpleCloseOverSet() { + static base::LazyInstance<UnicodeNonSimpleCloseOverSetData>::type set = + LAZY_INSTANCE_INITIALIZER; + return set.Pointer()->set; +} + + +} // namespace internal +} // namespace v8 +#endif // V8_INTL_SUPPORT diff --git a/js/src/irregexp/imported/special-case.h b/js/src/irregexp/imported/special-case.h new file mode 100644 index 0000000000..ea511af5a4 --- /dev/null +++ b/js/src/irregexp/imported/special-case.h @@ -0,0 +1,127 @@ +// 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. + +#ifndef V8_REGEXP_SPECIAL_CASE_H_ +#define V8_REGEXP_SPECIAL_CASE_H_ + +#ifdef V8_INTL_SUPPORT +#include "irregexp/RegExpShim.h" + +#include "unicode/uchar.h" +#include "unicode/uniset.h" +#include "unicode/unistr.h" + +namespace v8 { +namespace internal { + +// Sets of Unicode characters that need special handling under "i" mode + +// For non-unicode ignoreCase matches (aka "i", not "iu"), ECMA 262 +// defines slightly different case-folding rules than Unicode. An +// input character should match a pattern character if the result of +// the Canonicalize algorithm is the same for both characters. +// +// Roughly speaking, for "i" regexps, Canonicalize(c) is the same as +// c.toUpperCase(), unless a) c.toUpperCase() is a multi-character +// string, or b) c is non-ASCII, and c.toUpperCase() is ASCII. See +// https://tc39.es/ecma262/#sec-runtime-semantics-canonicalize-ch for +// the precise definition. +// +// While compiling such regular expressions, we need to compute the +// set of characters that should match a given input character. (See +// GetCaseIndependentLetters and CharacterRange::AddCaseEquivalents.) +// For almost all characters, this can be efficiently computed using +// UnicodeSet::closeOver(USET_CASE_INSENSITIVE). These sets represent +// the remaining special cases. +// +// For a character c, the rules are as follows: +// +// 1. If c is in neither IgnoreSet nor SpecialAddSet, then calling +// UnicodeSet::closeOver(USET_CASE_INSENSITIVE) on a UnicodeSet +// containing c will produce the set of characters that should +// match /c/i (or /[c]/i), and only those characters. +// +// 2. If c is in IgnoreSet, then the only character it should match is +// itself. However, closeOver will add additional incorrect +// matches. For example, consider SHARP S: 'ß' (U+00DF) and 'ẞ' +// (U+1E9E). Although closeOver('ß') = "ßẞ", uppercase('ß') is +// "SS". Step 3.e therefore requires that 'ß' canonicalizes to +// itself, and should not match 'ẞ'. In these cases, we can skip +// the closeOver entirely, because it will never add an equivalent +// character. +// +// 3. If c is in SpecialAddSet, then it should match at least one +// character other than itself. However, closeOver will add at +// least one additional incorrect match. For example, consider the +// letter 'k'. Closing over 'k' gives "kKK" (lowercase k, uppercase +// K, U+212A KELVIN SIGN). However, because of step 3.g, KELVIN +// SIGN should not match either of the other two characters. As a +// result, "k" and "K" are in SpecialAddSet (and KELVIN SIGN is in +// IgnoreSet). To find the correct matches for characters in +// SpecialAddSet, we closeOver the original character, but filter +// out the results that do not have the same canonical value. +// +// The contents of these sets are calculated at build time by +// src/regexp/gen-regexp-special-case.cc, which generates +// gen/src/regexp/special-case.cc. This is done by iterating over the +// 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) { + // a. Assert: ch is a UTF-16 code unit. + CHECK_LE(ch, 0xffff); + + // b. Let s be the String value consisting of the single code unit ch. + icu::UnicodeString s(ch); + + // c. Let u be the same result produced as if by performing the algorithm + // for String.prototype.toUpperCase using s as the this value. + // d. Assert: Type(u) is String. + icu::UnicodeString& u = s.toUpper(); + + // e. If u does not consist of a single code unit, return ch. + if (u.length() != 1) { + return ch; + } + + // f. Let cu be u's single code unit element. + UChar32 cu = u.char32At(0); + + // g. If the value of ch >= 128 and the value of cu < 128, return ch. + if (ch >= 128 && cu < 128) { + return ch; + } + + // h. Return cu. + return cu; + } +}; + +} // namespace internal +} // namespace v8 + +#endif // V8_INTL_SUPPORT + +#endif // V8_REGEXP_SPECIAL_CASE_H_ diff --git a/js/src/irregexp/moz.build b/js/src/irregexp/moz.build new file mode 100644 index 0000000000..ff030ad4bd --- /dev/null +++ b/js/src/irregexp/moz.build @@ -0,0 +1,49 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +FINAL_LIBRARY = "js" + +# Includes should be relative to parent path +LOCAL_INCLUDES += ["!..", ".."] + +include("../js-config.mozbuild") +include("../js-cxxflags.mozbuild") + +CXXFLAGS += ["-Wno-error=type-limits", "-Wno-error=return-type"] + +# Suppress spurious warnings in third-party code. See bug 1810584. +if CONFIG["CC_TYPE"] == "gcc": + CXXFLAGS += ["-Wno-error=nonnull"] + +UNIFIED_SOURCES += [ + "imported/regexp-bytecode-generator.cc", + "imported/regexp-bytecode-peephole.cc", + "imported/regexp-bytecodes.cc", + "imported/regexp-compiler-tonode.cc", + "imported/regexp-dotprinter.cc", + "imported/regexp-interpreter.cc", + "imported/regexp-macro-assembler-tracer.cc", + "imported/regexp-macro-assembler.cc", + "imported/regexp-parser.cc", + "imported/regexp-stack.cc", + "RegExpAPI.cpp", + "RegExpShim.cpp", + "util/UnicodeShim.cpp", +] +SOURCES += [ + "imported/regexp-ast.cc", + "imported/regexp-compiler.cc", # Bug 1643693 + "RegExpNativeMacroAssembler.cpp", +] + +if CONFIG["JS_HAS_INTL_API"]: + CXXFLAGS += ["-DV8_INTL_SUPPORT"] + UNIFIED_SOURCES += ["imported/property-sequences.cc", "imported/special-case.cc"] + +# Make sure all irregexp code is built with libfuzzer +# coverage instrumentation in FUZZING mode. +if CONFIG["FUZZING_INTERFACES"] and CONFIG["LIBFUZZER"]: + include("/tools/fuzzing/libfuzzer-config.mozbuild") diff --git a/js/src/irregexp/moz.yaml b/js/src/irregexp/moz.yaml new file mode 100644 index 0000000000..e230a89cfd --- /dev/null +++ b/js/src/irregexp/moz.yaml @@ -0,0 +1,36 @@ +schema: 1 + +bugzilla: + product: Core + component: "JavaScript Engine" + +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 + + license: BSD-3-Clause + license-file: LICENSE.v8 + +vendoring: + url: https://chromium.googlesource.com/v8/v8.git + source-hosting: googlesource + vendor-directory: js/src/irregexp/ + skip-vendoring-steps: ['fetch', 'move-contents'] + + update-actions: + - action: run-script + script: 'import-irregexp.py' + cwd: '{yaml_dir}' + +updatebot: + maintainer-phab: iain + maintainer-bz: iireland@mozilla.com + try-preset: sm-shell + tasks: + - type: vendoring + enabled: True + frequency: 1 week diff --git a/js/src/irregexp/util/FlagsShim.h b/js/src/irregexp/util/FlagsShim.h new file mode 100644 index 0000000000..28211a42bf --- /dev/null +++ b/js/src/irregexp/util/FlagsShim.h @@ -0,0 +1,93 @@ +// Copyright 2014 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. + +#ifndef V8_UTIL_FLAGS_H_ +#define V8_UTIL_FLAGS_H_ + +// Origin: +// https://github.com/v8/v8/blob/1bafcc6b999b23ea1d394f5d267a08183e3c4e19/src/base/flags.h#L15-L90 + +namespace v8 { +namespace base { + +// The Flags class provides a type-safe way of storing OR-combinations of enum +// values. The Flags<T, S> class is a template class, where T is an enum type, +// and S is the underlying storage type (usually int). +// +// The traditional C++ approach for storing OR-combinations of enum values is to +// use an int or unsigned int variable. The inconvenience with this approach is +// that there's no type checking at all; any enum value can be OR'd with any +// other enum value and passed on to a function that takes an int or unsigned +// int. +template <typename T, typename S = int> +class Flags final { + public: + using flag_type = T; + using mask_type = S; + + constexpr Flags() : mask_(0) {} + constexpr Flags(flag_type flag) : mask_(static_cast<S>(flag)) {} + constexpr explicit Flags(mask_type mask) : mask_(static_cast<S>(mask)) {} + + constexpr bool operator==(flag_type flag) const { + return mask_ == static_cast<S>(flag); + } + constexpr bool operator!=(flag_type flag) const { + return mask_ != static_cast<S>(flag); + } + + Flags& operator&=(const Flags& flags) { + mask_ &= flags.mask_; + return *this; + } + Flags& operator|=(const Flags& flags) { + mask_ |= flags.mask_; + return *this; + } + Flags& operator^=(const Flags& flags) { + mask_ ^= flags.mask_; + return *this; + } + + constexpr Flags operator&(const Flags& flags) const { + return Flags(mask_ & flags.mask_); + } + constexpr Flags operator|(const Flags& flags) const { + return Flags(mask_ | flags.mask_); + } + constexpr Flags operator^(const Flags& flags) const { + return Flags(mask_ ^ flags.mask_); + } + + Flags& operator&=(flag_type flag) { return operator&=(Flags(flag)); } + Flags& operator|=(flag_type flag) { return operator|=(Flags(flag)); } + Flags& operator^=(flag_type flag) { return operator^=(Flags(flag)); } + + constexpr Flags operator&(flag_type flag) const { + return operator&(Flags(flag)); + } + constexpr Flags operator|(flag_type flag) const { + return operator|(Flags(flag)); + } + constexpr Flags operator^(flag_type flag) const { + return operator^(Flags(flag)); + } + + constexpr Flags operator~() const { return Flags(~mask_); } + + constexpr operator mask_type() const { return mask_; } + constexpr bool operator!() const { return !mask_; } + + Flags without(flag_type flag) { return *this & (~Flags(flag)); } + + friend size_t hash_value(const Flags& flags) { return flags.mask_; } + + private: + mask_type mask_; +}; + +} // namespace base +} // namespace v8 + +#endif // V8_UTIL_FLAG_H_ diff --git a/js/src/irregexp/util/UnicodeShim.cpp b/js/src/irregexp/util/UnicodeShim.cpp new file mode 100644 index 0000000000..e2784e9e44 --- /dev/null +++ b/js/src/irregexp/util/UnicodeShim.cpp @@ -0,0 +1,1866 @@ +// Copyright 2012 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. +// +// This file is a subset of: +// https://github.com/v8/v8/blob/master/src/strings/unicode.cc + +#include "irregexp/RegExpShim.h" + +#ifdef V8_INTL_SUPPORT +# include "unicode/uchar.h" +#endif + +namespace v8 { +namespace unibrow { + +#ifndef V8_INTL_SUPPORT +static const int kStartBit = (1 << 30); +static const int kChunkBits = (1 << 13); +#endif // !V8_INTL_SUPPORT + +static const uchar kSentinel = static_cast<uchar>(-1); + +/** + * \file + * Implementations of functions for working with Unicode. + */ + +using int16_t = signed short; // NOLINT +using uint16_t = unsigned short; // NOLINT +using int32_t = int; // NOLINT + +#ifndef V8_INTL_SUPPORT +// All access to the character table should go through this function. +template <int D> +static inline uchar TableGet(const int32_t* table, int index) { + return table[D * index]; +} + +static inline uchar GetEntry(int32_t entry) { return entry & (kStartBit - 1); } + +static inline bool IsStart(int32_t entry) { return (entry & kStartBit) != 0; } + +/** + * Look up a character in the Unicode table using a mix of binary and + * interpolation search. For a uniformly distributed array + * interpolation search beats binary search by a wide margin. However, + * in this case interpolation search degenerates because of some very + * high values in the lower end of the table so this function uses a + * combination. The average number of steps to look up the information + * about a character is around 10, slightly higher if there is no + * information available about the character. + */ +static bool LookupPredicate(const int32_t* table, uint16_t size, uchar chr) { + static const int kEntryDist = 1; + uint16_t value = chr & (kChunkBits - 1); + unsigned int low = 0; + unsigned int high = size - 1; + while (high != low) { + unsigned int mid = low + ((high - low) >> 1); + uchar current_value = GetEntry(TableGet<kEntryDist>(table, mid)); + // If we've found an entry less than or equal to this one, and the + // next one is not also less than this one, we've arrived. + if ((current_value <= value) && + (mid + 1 == size || + GetEntry(TableGet<kEntryDist>(table, mid + 1)) > value)) { + low = mid; + break; + } else if (current_value < value) { + low = mid + 1; + } else if (current_value > value) { + // If we've just checked the bottom-most value and it's not + // the one we're looking for, we're done. + if (mid == 0) break; + high = mid - 1; + } + } + int32_t field = TableGet<kEntryDist>(table, low); + uchar entry = GetEntry(field); + bool is_start = IsStart(field); + return (entry == value) || (entry < value && is_start); +} +#endif // !V8_INTL_SUPPORT + +template <int kW> +struct MultiCharacterSpecialCase { + static const uchar kEndOfEncoding = kSentinel; + uchar chars[kW]; +}; + +#ifndef V8_INTL_SUPPORT +// Look up the mapping for the given character in the specified table, +// which is of the specified length and uses the specified special case +// mapping for multi-char mappings. The next parameter is the character +// following the one to map. The result will be written in to the result +// buffer and the number of characters written will be returned. Finally, +// if the allow_caching_ptr is non-null then false will be stored in +// it if the result contains multiple characters or depends on the +// context. +// If ranges are linear, a match between a start and end point is +// offset by the distance between the match and the start. Otherwise +// the result is the same as for the start point on the entire range. +template <bool ranges_are_linear, int kW> +static int LookupMapping(const int32_t* table, uint16_t size, + const MultiCharacterSpecialCase<kW>* multi_chars, + uchar chr, uchar next, uchar* result, + bool* allow_caching_ptr) { + static const int kEntryDist = 2; + uint16_t key = chr & (kChunkBits - 1); + uint16_t chunk_start = chr - key; + unsigned int low = 0; + unsigned int high = size - 1; + while (high != low) { + unsigned int mid = low + ((high - low) >> 1); + uchar current_value = GetEntry(TableGet<kEntryDist>(table, mid)); + // If we've found an entry less than or equal to this one, and the next one + // is not also less than this one, we've arrived. + if ((current_value <= key) && + (mid + 1 == size || + GetEntry(TableGet<kEntryDist>(table, mid + 1)) > key)) { + low = mid; + break; + } else if (current_value < key) { + low = mid + 1; + } else if (current_value > key) { + // If we've just checked the bottom-most value and it's not + // the one we're looking for, we're done. + if (mid == 0) break; + high = mid - 1; + } + } + int32_t field = TableGet<kEntryDist>(table, low); + uchar entry = GetEntry(field); + bool is_start = IsStart(field); + bool found = (entry == key) || (entry < key && is_start); + if (found) { + int32_t value = table[2 * low + 1]; + if (value == 0) { + // 0 means not present + return 0; + } else if ((value & 3) == 0) { + // Low bits 0 means a constant offset from the given character. + if (ranges_are_linear) { + result[0] = chr + (value >> 2); + } else { + result[0] = entry + chunk_start + (value >> 2); + } + return 1; + } else if ((value & 3) == 1) { + // Low bits 1 means a special case mapping + if (allow_caching_ptr) *allow_caching_ptr = false; + const MultiCharacterSpecialCase<kW>& mapping = multi_chars[value >> 2]; + int length = 0; + for (length = 0; length < kW; length++) { + uchar mapped = mapping.chars[length]; + if (mapped == MultiCharacterSpecialCase<kW>::kEndOfEncoding) break; + if (ranges_are_linear) { + result[length] = mapped + (key - entry); + } else { + result[length] = mapped; + } + } + return length; + } else { + // Low bits 2 means a really really special case + if (allow_caching_ptr) *allow_caching_ptr = false; + // The cases of this switch are defined in unicode.py in the + // really_special_cases mapping. + switch (value >> 2) { + case 1: + // Really special case 1: upper case sigma. This letter + // converts to two different lower case sigmas depending on + // whether or not it occurs at the end of a word. + if (next != 0 && Letter::Is(next)) { + result[0] = 0x03C3; + } else { + result[0] = 0x03C2; + } + return 1; + default: + return 0; + } + return -1; + } + } else { + return 0; + } +} +#endif // !V8_INTL_SUPPORT + +// Letter: point.category in ['Lu', 'Ll', 'Lt', 'Lm', 'Lo', 'Nl'] +#ifdef V8_INTL_SUPPORT +bool Letter::Is(uchar c) { return static_cast<bool>(u_isalpha(c)); } +#else +static const uint16_t kLetterTable0Size = 431; +static const int32_t kLetterTable0[431] = { + 1073741889, 90, 1073741921, 122, + 170, 181, 186, 1073742016, // NOLINT + 214, 1073742040, 246, 1073742072, + 705, 1073742534, 721, 1073742560, // NOLINT + 740, 748, 750, 1073742704, + 884, 1073742710, 887, 1073742714, // NOLINT + 893, 895, 902, 1073742728, + 906, 908, 1073742734, 929, // NOLINT + 1073742755, 1013, 1073742839, 1153, + 1073742986, 1327, 1073743153, 1366, // NOLINT + 1369, 1073743201, 1415, 1073743312, + 1514, 1073743344, 1522, 1073743392, // NOLINT + 1610, 1073743470, 1647, 1073743473, + 1747, 1749, 1073743589, 1766, // NOLINT + 1073743598, 1775, 1073743610, 1788, + 1791, 1808, 1073743634, 1839, // NOLINT + 1073743693, 1957, 1969, 1073743818, + 2026, 1073743860, 2037, 2042, // NOLINT + 1073743872, 2069, 2074, 2084, + 2088, 1073743936, 2136, 1073744032, // NOLINT + 2226, 1073744132, 2361, 2365, + 2384, 1073744216, 2401, 1073744241, // NOLINT + 2432, 1073744261, 2444, 1073744271, + 2448, 1073744275, 2472, 1073744298, // NOLINT + 2480, 2482, 1073744310, 2489, + 2493, 2510, 1073744348, 2525, // NOLINT + 1073744351, 2529, 1073744368, 2545, + 1073744389, 2570, 1073744399, 2576, // NOLINT + 1073744403, 2600, 1073744426, 2608, + 1073744434, 2611, 1073744437, 2614, // NOLINT + 1073744440, 2617, 1073744473, 2652, + 2654, 1073744498, 2676, 1073744517, // NOLINT + 2701, 1073744527, 2705, 1073744531, + 2728, 1073744554, 2736, 1073744562, // NOLINT + 2739, 1073744565, 2745, 2749, + 2768, 1073744608, 2785, 1073744645, // NOLINT + 2828, 1073744655, 2832, 1073744659, + 2856, 1073744682, 2864, 1073744690, // NOLINT + 2867, 1073744693, 2873, 2877, + 1073744732, 2909, 1073744735, 2913, // NOLINT + 2929, 2947, 1073744773, 2954, + 1073744782, 2960, 1073744786, 2965, // NOLINT + 1073744793, 2970, 2972, 1073744798, + 2975, 1073744803, 2980, 1073744808, // NOLINT + 2986, 1073744814, 3001, 3024, + 1073744901, 3084, 1073744910, 3088, // NOLINT + 1073744914, 3112, 1073744938, 3129, + 3133, 1073744984, 3161, 1073744992, // NOLINT + 3169, 1073745029, 3212, 1073745038, + 3216, 1073745042, 3240, 1073745066, // NOLINT + 3251, 1073745077, 3257, 3261, + 3294, 1073745120, 3297, 1073745137, // NOLINT + 3314, 1073745157, 3340, 1073745166, + 3344, 1073745170, 3386, 3389, // NOLINT + 3406, 1073745248, 3425, 1073745274, + 3455, 1073745285, 3478, 1073745306, // NOLINT + 3505, 1073745331, 3515, 3517, + 1073745344, 3526, 1073745409, 3632, // NOLINT + 1073745458, 3635, 1073745472, 3654, + 1073745537, 3714, 3716, 1073745543, // NOLINT + 3720, 3722, 3725, 1073745556, + 3735, 1073745561, 3743, 1073745569, // NOLINT + 3747, 3749, 3751, 1073745578, + 3755, 1073745581, 3760, 1073745586, // NOLINT + 3763, 3773, 1073745600, 3780, + 3782, 1073745628, 3807, 3840, // NOLINT + 1073745728, 3911, 1073745737, 3948, + 1073745800, 3980, 1073745920, 4138, // NOLINT + 4159, 1073746000, 4181, 1073746010, + 4189, 4193, 1073746021, 4198, // NOLINT + 1073746030, 4208, 1073746037, 4225, + 4238, 1073746080, 4293, 4295, // NOLINT + 4301, 1073746128, 4346, 1073746172, + 4680, 1073746506, 4685, 1073746512, // NOLINT + 4694, 4696, 1073746522, 4701, + 1073746528, 4744, 1073746570, 4749, // NOLINT + 1073746576, 4784, 1073746610, 4789, + 1073746616, 4798, 4800, 1073746626, // NOLINT + 4805, 1073746632, 4822, 1073746648, + 4880, 1073746706, 4885, 1073746712, // NOLINT + 4954, 1073746816, 5007, 1073746848, + 5108, 1073746945, 5740, 1073747567, // NOLINT + 5759, 1073747585, 5786, 1073747616, + 5866, 1073747694, 5880, 1073747712, // NOLINT + 5900, 1073747726, 5905, 1073747744, + 5937, 1073747776, 5969, 1073747808, // NOLINT + 5996, 1073747822, 6000, 1073747840, + 6067, 6103, 6108, 1073748000, // NOLINT + 6263, 1073748096, 6312, 6314, + 1073748144, 6389, 1073748224, 6430, // NOLINT + 1073748304, 6509, 1073748336, 6516, + 1073748352, 6571, 1073748417, 6599, // NOLINT + 1073748480, 6678, 1073748512, 6740, + 6823, 1073748741, 6963, 1073748805, // NOLINT + 6987, 1073748867, 7072, 1073748910, + 7087, 1073748922, 7141, 1073748992, // NOLINT + 7203, 1073749069, 7247, 1073749082, + 7293, 1073749225, 7404, 1073749230, // NOLINT + 7409, 1073749237, 7414, 1073749248, + 7615, 1073749504, 7957, 1073749784, // NOLINT + 7965, 1073749792, 8005, 1073749832, + 8013, 1073749840, 8023, 8025, // NOLINT + 8027, 8029, 1073749855, 8061, + 1073749888, 8116, 1073749942, 8124, // NOLINT + 8126, 1073749954, 8132, 1073749958, + 8140, 1073749968, 8147, 1073749974, // NOLINT + 8155, 1073749984, 8172, 1073750002, + 8180, 1073750006, 8188}; // NOLINT +static const uint16_t kLetterTable1Size = 87; +static const int32_t kLetterTable1[87] = { + 113, 127, 1073741968, 156, + 258, 263, 1073742090, 275, // NOLINT + 277, 1073742105, 285, 292, + 294, 296, 1073742122, 301, // NOLINT + 1073742127, 313, 1073742140, 319, + 1073742149, 329, 334, 1073742176, // NOLINT + 392, 1073744896, 3118, 1073744944, + 3166, 1073744992, 3300, 1073745131, // NOLINT + 3310, 1073745138, 3315, 1073745152, + 3365, 3367, 3373, 1073745200, // NOLINT + 3431, 3439, 1073745280, 3478, + 1073745312, 3494, 1073745320, 3502, // NOLINT + 1073745328, 3510, 1073745336, 3518, + 1073745344, 3526, 1073745352, 3534, // NOLINT + 1073745360, 3542, 1073745368, 3550, + 3631, 1073745925, 4103, 1073745953, // NOLINT + 4137, 1073745969, 4149, 1073745976, + 4156, 1073745985, 4246, 1073746077, // NOLINT + 4255, 1073746081, 4346, 1073746172, + 4351, 1073746181, 4397, 1073746225, // NOLINT + 4494, 1073746336, 4538, 1073746416, + 4607, 1073746944, 8191}; // NOLINT +static const uint16_t kLetterTable2Size = 4; +static const int32_t kLetterTable2[4] = {1073741824, 3509, 1073745408, + 8191}; // NOLINT +static const uint16_t kLetterTable3Size = 2; +static const int32_t kLetterTable3[2] = {1073741824, 8191}; // NOLINT +static const uint16_t kLetterTable4Size = 2; +static const int32_t kLetterTable4[2] = {1073741824, 8140}; // NOLINT +static const uint16_t kLetterTable5Size = 100; +static const int32_t kLetterTable5[100] = { + 1073741824, 1164, 1073743056, 1277, + 1073743104, 1548, 1073743376, 1567, // NOLINT + 1073743402, 1579, 1073743424, 1646, + 1073743487, 1693, 1073743520, 1775, // NOLINT + 1073743639, 1823, 1073743650, 1928, + 1073743755, 1934, 1073743760, 1965, // NOLINT + 1073743792, 1969, 1073743863, 2049, + 1073743875, 2053, 1073743879, 2058, // NOLINT + 1073743884, 2082, 1073743936, 2163, + 1073744002, 2227, 1073744114, 2295, // NOLINT + 2299, 1073744138, 2341, 1073744176, + 2374, 1073744224, 2428, 1073744260, // NOLINT + 2482, 2511, 1073744352, 2532, + 1073744358, 2543, 1073744378, 2558, // NOLINT + 1073744384, 2600, 1073744448, 2626, + 1073744452, 2635, 1073744480, 2678, // NOLINT + 2682, 1073744510, 2735, 2737, + 1073744565, 2742, 1073744569, 2749, // NOLINT + 2752, 2754, 1073744603, 2781, + 1073744608, 2794, 1073744626, 2804, // NOLINT + 1073744641, 2822, 1073744649, 2830, + 1073744657, 2838, 1073744672, 2854, // NOLINT + 1073744680, 2862, 1073744688, 2906, + 1073744732, 2911, 1073744740, 2917, // NOLINT + 1073744832, 3042, 1073744896, 8191}; // NOLINT +static const uint16_t kLetterTable6Size = 6; +static const int32_t kLetterTable6[6] = {1073741824, 6051, 1073747888, 6086, + 1073747915, 6139}; // NOLINT +static const uint16_t kLetterTable7Size = 48; +static const int32_t kLetterTable7[48] = { + 1073748224, 6765, 1073748592, 6873, + 1073748736, 6918, 1073748755, 6935, // NOLINT + 6941, 1073748767, 6952, 1073748778, + 6966, 1073748792, 6972, 6974, // NOLINT + 1073748800, 6977, 1073748803, 6980, + 1073748806, 7089, 1073748947, 7485, // NOLINT + 1073749328, 7567, 1073749394, 7623, + 1073749488, 7675, 1073749616, 7796, // NOLINT + 1073749622, 7932, 1073749793, 7994, + 1073749825, 8026, 1073749862, 8126, // NOLINT + 1073749954, 8135, 1073749962, 8143, + 1073749970, 8151, 1073749978, 8156}; // NOLINT +bool Letter::Is(uchar c) { + int chunk_index = c >> 13; + switch (chunk_index) { + case 0: + return LookupPredicate(kLetterTable0, kLetterTable0Size, c); + case 1: + return LookupPredicate(kLetterTable1, kLetterTable1Size, c); + case 2: + return LookupPredicate(kLetterTable2, kLetterTable2Size, c); + case 3: + return LookupPredicate(kLetterTable3, kLetterTable3Size, c); + case 4: + return LookupPredicate(kLetterTable4, kLetterTable4Size, c); + case 5: + return LookupPredicate(kLetterTable5, kLetterTable5Size, c); + case 6: + return LookupPredicate(kLetterTable6, kLetterTable6Size, c); + case 7: + return LookupPredicate(kLetterTable7, kLetterTable7Size, c); + default: + return false; + } +} +#endif + +#ifndef V8_INTL_SUPPORT + +static const MultiCharacterSpecialCase<1> kEcma262CanonicalizeMultiStrings0[1] = + { // NOLINT + {{kSentinel}}}; // NOLINT +static const uint16_t kEcma262CanonicalizeTable0Size = 498; // NOLINT +static const int32_t kEcma262CanonicalizeTable0[996] = { + 1073741921, -128, 122, -128, 181, 2972, + 1073742048, -128, 246, -128, 1073742072, -128, + 254, -128, 255, 484, // NOLINT + 257, -4, 259, -4, 261, -4, + 263, -4, 265, -4, 267, -4, + 269, -4, 271, -4, // NOLINT + 273, -4, 275, -4, 277, -4, + 279, -4, 281, -4, 283, -4, + 285, -4, 287, -4, // NOLINT + 289, -4, 291, -4, 293, -4, + 295, -4, 297, -4, 299, -4, + 301, -4, 303, -4, // NOLINT + 307, -4, 309, -4, 311, -4, + 314, -4, 316, -4, 318, -4, + 320, -4, 322, -4, // NOLINT + 324, -4, 326, -4, 328, -4, + 331, -4, 333, -4, 335, -4, + 337, -4, 339, -4, // NOLINT + 341, -4, 343, -4, 345, -4, + 347, -4, 349, -4, 351, -4, + 353, -4, 355, -4, // NOLINT + 357, -4, 359, -4, 361, -4, + 363, -4, 365, -4, 367, -4, + 369, -4, 371, -4, // NOLINT + 373, -4, 375, -4, 378, -4, + 380, -4, 382, -4, 384, 780, + 387, -4, 389, -4, // NOLINT + 392, -4, 396, -4, 402, -4, + 405, 388, 409, -4, 410, 652, + 414, 520, 417, -4, // NOLINT + 419, -4, 421, -4, 424, -4, + 429, -4, 432, -4, 436, -4, + 438, -4, 441, -4, // NOLINT + 445, -4, 447, 224, 453, -4, + 454, -8, 456, -4, 457, -8, + 459, -4, 460, -8, // NOLINT + 462, -4, 464, -4, 466, -4, + 468, -4, 470, -4, 472, -4, + 474, -4, 476, -4, // NOLINT + 477, -316, 479, -4, 481, -4, + 483, -4, 485, -4, 487, -4, + 489, -4, 491, -4, // NOLINT + 493, -4, 495, -4, 498, -4, + 499, -8, 501, -4, 505, -4, + 507, -4, 509, -4, // NOLINT + 511, -4, 513, -4, 515, -4, + 517, -4, 519, -4, 521, -4, + 523, -4, 525, -4, // NOLINT + 527, -4, 529, -4, 531, -4, + 533, -4, 535, -4, 537, -4, + 539, -4, 541, -4, // NOLINT + 543, -4, 547, -4, 549, -4, + 551, -4, 553, -4, 555, -4, + 557, -4, 559, -4, // NOLINT + 561, -4, 563, -4, 572, -4, + 1073742399, 43260, 576, 43260, 578, -4, + 583, -4, 585, -4, // NOLINT + 587, -4, 589, -4, 591, -4, + 592, 43132, 593, 43120, 594, 43128, + 595, -840, 596, -824, // NOLINT + 1073742422, -820, 599, -820, 601, -808, + 603, -812, 604, 169276, 608, -820, + 609, 169260, 611, -828, // NOLINT + 613, 169120, 614, 169232, 616, -836, + 617, -844, 619, 42972, 620, 169220, + 623, -844, 625, 42996, // NOLINT + 626, -852, 629, -856, 637, 42908, + 640, -872, 643, -872, 647, 169128, + 648, -872, 649, -276, // NOLINT + 1073742474, -868, 651, -868, 652, -284, + 658, -876, 670, 169032, 837, 336, + 881, -4, 883, -4, // NOLINT + 887, -4, 1073742715, 520, 893, 520, + 940, -152, 1073742765, -148, 943, -148, + 1073742769, -128, 961, -128, // NOLINT + 962, -124, 1073742787, -128, 971, -128, + 972, -256, 1073742797, -252, 974, -252, + 976, -248, 977, -228, // NOLINT + 981, -188, 982, -216, 983, -32, + 985, -4, 987, -4, 989, -4, + 991, -4, 993, -4, // NOLINT + 995, -4, 997, -4, 999, -4, + 1001, -4, 1003, -4, 1005, -4, + 1007, -4, 1008, -344, // NOLINT + 1009, -320, 1010, 28, 1011, -464, + 1013, -384, 1016, -4, 1019, -4, + 1073742896, -128, 1103, -128, // NOLINT + 1073742928, -320, 1119, -320, 1121, -4, + 1123, -4, 1125, -4, 1127, -4, + 1129, -4, 1131, -4, // NOLINT + 1133, -4, 1135, -4, 1137, -4, + 1139, -4, 1141, -4, 1143, -4, + 1145, -4, 1147, -4, // NOLINT + 1149, -4, 1151, -4, 1153, -4, + 1163, -4, 1165, -4, 1167, -4, + 1169, -4, 1171, -4, // NOLINT + 1173, -4, 1175, -4, 1177, -4, + 1179, -4, 1181, -4, 1183, -4, + 1185, -4, 1187, -4, // NOLINT + 1189, -4, 1191, -4, 1193, -4, + 1195, -4, 1197, -4, 1199, -4, + 1201, -4, 1203, -4, // NOLINT + 1205, -4, 1207, -4, 1209, -4, + 1211, -4, 1213, -4, 1215, -4, + 1218, -4, 1220, -4, // NOLINT + 1222, -4, 1224, -4, 1226, -4, + 1228, -4, 1230, -4, 1231, -60, + 1233, -4, 1235, -4, // NOLINT + 1237, -4, 1239, -4, 1241, -4, + 1243, -4, 1245, -4, 1247, -4, + 1249, -4, 1251, -4, // NOLINT + 1253, -4, 1255, -4, 1257, -4, + 1259, -4, 1261, -4, 1263, -4, + 1265, -4, 1267, -4, // NOLINT + 1269, -4, 1271, -4, 1273, -4, + 1275, -4, 1277, -4, 1279, -4, + 1281, -4, 1283, -4, // NOLINT + 1285, -4, 1287, -4, 1289, -4, + 1291, -4, 1293, -4, 1295, -4, + 1297, -4, 1299, -4, // NOLINT + 1301, -4, 1303, -4, 1305, -4, + 1307, -4, 1309, -4, 1311, -4, + 1313, -4, 1315, -4, // NOLINT + 1317, -4, 1319, -4, 1321, -4, + 1323, -4, 1325, -4, 1327, -4, + 1073743201, -192, 1414, -192, // NOLINT + 7545, 141328, 7549, 15256, 7681, -4, + 7683, -4, 7685, -4, 7687, -4, + 7689, -4, 7691, -4, // NOLINT + 7693, -4, 7695, -4, 7697, -4, + 7699, -4, 7701, -4, 7703, -4, + 7705, -4, 7707, -4, // NOLINT + 7709, -4, 7711, -4, 7713, -4, + 7715, -4, 7717, -4, 7719, -4, + 7721, -4, 7723, -4, // NOLINT + 7725, -4, 7727, -4, 7729, -4, + 7731, -4, 7733, -4, 7735, -4, + 7737, -4, 7739, -4, // NOLINT + 7741, -4, 7743, -4, 7745, -4, + 7747, -4, 7749, -4, 7751, -4, + 7753, -4, 7755, -4, // NOLINT + 7757, -4, 7759, -4, 7761, -4, + 7763, -4, 7765, -4, 7767, -4, + 7769, -4, 7771, -4, // NOLINT + 7773, -4, 7775, -4, 7777, -4, + 7779, -4, 7781, -4, 7783, -4, + 7785, -4, 7787, -4, // NOLINT + 7789, -4, 7791, -4, 7793, -4, + 7795, -4, 7797, -4, 7799, -4, + 7801, -4, 7803, -4, // NOLINT + 7805, -4, 7807, -4, 7809, -4, + 7811, -4, 7813, -4, 7815, -4, + 7817, -4, 7819, -4, // NOLINT + 7821, -4, 7823, -4, 7825, -4, + 7827, -4, 7829, -4, 7835, -236, + 7841, -4, 7843, -4, // NOLINT + 7845, -4, 7847, -4, 7849, -4, + 7851, -4, 7853, -4, 7855, -4, + 7857, -4, 7859, -4, // NOLINT + 7861, -4, 7863, -4, 7865, -4, + 7867, -4, 7869, -4, 7871, -4, + 7873, -4, 7875, -4, // NOLINT + 7877, -4, 7879, -4, 7881, -4, + 7883, -4, 7885, -4, 7887, -4, + 7889, -4, 7891, -4, // NOLINT + 7893, -4, 7895, -4, 7897, -4, + 7899, -4, 7901, -4, 7903, -4, + 7905, -4, 7907, -4, // NOLINT + 7909, -4, 7911, -4, 7913, -4, + 7915, -4, 7917, -4, 7919, -4, + 7921, -4, 7923, -4, // NOLINT + 7925, -4, 7927, -4, 7929, -4, + 7931, -4, 7933, -4, 7935, -4, + 1073749760, 32, 7943, 32, // NOLINT + 1073749776, 32, 7957, 32, 1073749792, 32, + 7975, 32, 1073749808, 32, 7991, 32, + 1073749824, 32, 8005, 32, // NOLINT + 8017, 32, 8019, 32, 8021, 32, + 8023, 32, 1073749856, 32, 8039, 32, + 1073749872, 296, 8049, 296, // NOLINT + 1073749874, 344, 8053, 344, 1073749878, 400, + 8055, 400, 1073749880, 512, 8057, 512, + 1073749882, 448, 8059, 448, // NOLINT + 1073749884, 504, 8061, 504, 1073749936, 32, + 8113, 32, 8126, -28820, 1073749968, 32, + 8145, 32, 1073749984, 32, // NOLINT + 8161, 32, 8165, 28}; // NOLINT +static const MultiCharacterSpecialCase<1> kEcma262CanonicalizeMultiStrings1[1] = + { // NOLINT + {{kSentinel}}}; // NOLINT +static const uint16_t kEcma262CanonicalizeTable1Size = 73; // NOLINT +static const int32_t kEcma262CanonicalizeTable1[146] = { + 334, -112, 1073742192, -64, 383, -64, + 388, -4, 1073743056, -104, 1257, -104, + 1073744944, -192, 3166, -192, // NOLINT + 3169, -4, 3173, -43180, 3174, -43168, + 3176, -4, 3178, -4, 3180, -4, + 3187, -4, 3190, -4, // NOLINT + 3201, -4, 3203, -4, 3205, -4, + 3207, -4, 3209, -4, 3211, -4, + 3213, -4, 3215, -4, // NOLINT + 3217, -4, 3219, -4, 3221, -4, + 3223, -4, 3225, -4, 3227, -4, + 3229, -4, 3231, -4, // NOLINT + 3233, -4, 3235, -4, 3237, -4, + 3239, -4, 3241, -4, 3243, -4, + 3245, -4, 3247, -4, // NOLINT + 3249, -4, 3251, -4, 3253, -4, + 3255, -4, 3257, -4, 3259, -4, + 3261, -4, 3263, -4, // NOLINT + 3265, -4, 3267, -4, 3269, -4, + 3271, -4, 3273, -4, 3275, -4, + 3277, -4, 3279, -4, // NOLINT + 3281, -4, 3283, -4, 3285, -4, + 3287, -4, 3289, -4, 3291, -4, + 3293, -4, 3295, -4, // NOLINT + 3297, -4, 3299, -4, 3308, -4, + 3310, -4, 3315, -4, 1073745152, -29056, + 3365, -29056, 3367, -29056, // NOLINT + 3373, -29056}; // NOLINT +static const MultiCharacterSpecialCase<1> kEcma262CanonicalizeMultiStrings5[1] = + { // NOLINT + {{kSentinel}}}; // NOLINT +static const uint16_t kEcma262CanonicalizeTable5Size = 95; // NOLINT +static const int32_t kEcma262CanonicalizeTable5[190] = + { + 1601, -4, 1603, -4, 1605, -4, 1607, -4, + 1609, -4, 1611, -4, 1613, -4, 1615, -4, // NOLINT + 1617, -4, 1619, -4, 1621, -4, 1623, -4, + 1625, -4, 1627, -4, 1629, -4, 1631, -4, // NOLINT + 1633, -4, 1635, -4, 1637, -4, 1639, -4, + 1641, -4, 1643, -4, 1645, -4, 1665, -4, // NOLINT + 1667, -4, 1669, -4, 1671, -4, 1673, -4, + 1675, -4, 1677, -4, 1679, -4, 1681, -4, // NOLINT + 1683, -4, 1685, -4, 1687, -4, 1689, -4, + 1691, -4, 1827, -4, 1829, -4, 1831, -4, // NOLINT + 1833, -4, 1835, -4, 1837, -4, 1839, -4, + 1843, -4, 1845, -4, 1847, -4, 1849, -4, // NOLINT + 1851, -4, 1853, -4, 1855, -4, 1857, -4, + 1859, -4, 1861, -4, 1863, -4, 1865, -4, // NOLINT + 1867, -4, 1869, -4, 1871, -4, 1873, -4, + 1875, -4, 1877, -4, 1879, -4, 1881, -4, // NOLINT + 1883, -4, 1885, -4, 1887, -4, 1889, -4, + 1891, -4, 1893, -4, 1895, -4, 1897, -4, // NOLINT + 1899, -4, 1901, -4, 1903, -4, 1914, -4, + 1916, -4, 1919, -4, 1921, -4, 1923, -4, // NOLINT + 1925, -4, 1927, -4, 1932, -4, 1937, -4, + 1939, -4, 1943, -4, 1945, -4, 1947, -4, // NOLINT + 1949, -4, 1951, -4, 1953, -4, 1955, -4, + 1957, -4, 1959, -4, 1961, -4}; // NOLINT +static const MultiCharacterSpecialCase<1> kEcma262CanonicalizeMultiStrings7[1] = + { // NOLINT + {{kSentinel}}}; // NOLINT +static const uint16_t kEcma262CanonicalizeTable7Size = 2; // NOLINT +static const int32_t kEcma262CanonicalizeTable7[4] = {1073749825, -128, 8026, + -128}; // NOLINT +int Ecma262Canonicalize::Convert(uchar c, uchar n, uchar* result, + bool* allow_caching_ptr) { + int chunk_index = c >> 13; + switch (chunk_index) { + case 0: + return LookupMapping<true>( + kEcma262CanonicalizeTable0, kEcma262CanonicalizeTable0Size, + kEcma262CanonicalizeMultiStrings0, c, n, result, allow_caching_ptr); + case 1: + return LookupMapping<true>( + kEcma262CanonicalizeTable1, kEcma262CanonicalizeTable1Size, + kEcma262CanonicalizeMultiStrings1, c, n, result, allow_caching_ptr); + case 5: + return LookupMapping<true>( + kEcma262CanonicalizeTable5, kEcma262CanonicalizeTable5Size, + kEcma262CanonicalizeMultiStrings5, c, n, result, allow_caching_ptr); + case 7: + return LookupMapping<true>( + kEcma262CanonicalizeTable7, kEcma262CanonicalizeTable7Size, + kEcma262CanonicalizeMultiStrings7, c, n, result, allow_caching_ptr); + default: + return 0; + } +} + +static const MultiCharacterSpecialCase<4> + kEcma262UnCanonicalizeMultiStrings0[507] = { // NOLINT + {{65, 97, kSentinel}}, + {{90, 122, kSentinel}}, + {{181, 924, 956, kSentinel}}, + {{192, 224, kSentinel}}, // NOLINT + {{214, 246, kSentinel}}, + {{216, 248, kSentinel}}, + {{222, 254, kSentinel}}, + {{255, 376, kSentinel}}, // NOLINT + {{256, 257, kSentinel}}, + {{258, 259, kSentinel}}, + {{260, 261, kSentinel}}, + {{262, 263, kSentinel}}, // NOLINT + {{264, 265, kSentinel}}, + {{266, 267, kSentinel}}, + {{268, 269, kSentinel}}, + {{270, 271, kSentinel}}, // NOLINT + {{272, 273, kSentinel}}, + {{274, 275, kSentinel}}, + {{276, 277, kSentinel}}, + {{278, 279, kSentinel}}, // NOLINT + {{280, 281, kSentinel}}, + {{282, 283, kSentinel}}, + {{284, 285, kSentinel}}, + {{286, 287, kSentinel}}, // NOLINT + {{288, 289, kSentinel}}, + {{290, 291, kSentinel}}, + {{292, 293, kSentinel}}, + {{294, 295, kSentinel}}, // NOLINT + {{296, 297, kSentinel}}, + {{298, 299, kSentinel}}, + {{300, 301, kSentinel}}, + {{302, 303, kSentinel}}, // NOLINT + {{306, 307, kSentinel}}, + {{308, 309, kSentinel}}, + {{310, 311, kSentinel}}, + {{313, 314, kSentinel}}, // NOLINT + {{315, 316, kSentinel}}, + {{317, 318, kSentinel}}, + {{319, 320, kSentinel}}, + {{321, 322, kSentinel}}, // NOLINT + {{323, 324, kSentinel}}, + {{325, 326, kSentinel}}, + {{327, 328, kSentinel}}, + {{330, 331, kSentinel}}, // NOLINT + {{332, 333, kSentinel}}, + {{334, 335, kSentinel}}, + {{336, 337, kSentinel}}, + {{338, 339, kSentinel}}, // NOLINT + {{340, 341, kSentinel}}, + {{342, 343, kSentinel}}, + {{344, 345, kSentinel}}, + {{346, 347, kSentinel}}, // NOLINT + {{348, 349, kSentinel}}, + {{350, 351, kSentinel}}, + {{352, 353, kSentinel}}, + {{354, 355, kSentinel}}, // NOLINT + {{356, 357, kSentinel}}, + {{358, 359, kSentinel}}, + {{360, 361, kSentinel}}, + {{362, 363, kSentinel}}, // NOLINT + {{364, 365, kSentinel}}, + {{366, 367, kSentinel}}, + {{368, 369, kSentinel}}, + {{370, 371, kSentinel}}, // NOLINT + {{372, 373, kSentinel}}, + {{374, 375, kSentinel}}, + {{377, 378, kSentinel}}, + {{379, 380, kSentinel}}, // NOLINT + {{381, 382, kSentinel}}, + {{384, 579, kSentinel}}, + {{385, 595, kSentinel}}, + {{386, 387, kSentinel}}, // NOLINT + {{388, 389, kSentinel}}, + {{390, 596, kSentinel}}, + {{391, 392, kSentinel}}, + {{393, 598, kSentinel}}, // NOLINT + {{394, 599, kSentinel}}, + {{395, 396, kSentinel}}, + {{398, 477, kSentinel}}, + {{399, 601, kSentinel}}, // NOLINT + {{400, 603, kSentinel}}, + {{401, 402, kSentinel}}, + {{403, 608, kSentinel}}, + {{404, 611, kSentinel}}, // NOLINT + {{405, 502, kSentinel}}, + {{406, 617, kSentinel}}, + {{407, 616, kSentinel}}, + {{408, 409, kSentinel}}, // NOLINT + {{410, 573, kSentinel}}, + {{412, 623, kSentinel}}, + {{413, 626, kSentinel}}, + {{414, 544, kSentinel}}, // NOLINT + {{415, 629, kSentinel}}, + {{416, 417, kSentinel}}, + {{418, 419, kSentinel}}, + {{420, 421, kSentinel}}, // NOLINT + {{422, 640, kSentinel}}, + {{423, 424, kSentinel}}, + {{425, 643, kSentinel}}, + {{428, 429, kSentinel}}, // NOLINT + {{430, 648, kSentinel}}, + {{431, 432, kSentinel}}, + {{433, 650, kSentinel}}, + {{434, 651, kSentinel}}, // NOLINT + {{435, 436, kSentinel}}, + {{437, 438, kSentinel}}, + {{439, 658, kSentinel}}, + {{440, 441, kSentinel}}, // NOLINT + {{444, 445, kSentinel}}, + {{447, 503, kSentinel}}, + {{452, 453, 454, kSentinel}}, + {{455, 456, 457, kSentinel}}, // NOLINT + {{458, 459, 460, kSentinel}}, + {{461, 462, kSentinel}}, + {{463, 464, kSentinel}}, + {{465, 466, kSentinel}}, // NOLINT + {{467, 468, kSentinel}}, + {{469, 470, kSentinel}}, + {{471, 472, kSentinel}}, + {{473, 474, kSentinel}}, // NOLINT + {{475, 476, kSentinel}}, + {{478, 479, kSentinel}}, + {{480, 481, kSentinel}}, + {{482, 483, kSentinel}}, // NOLINT + {{484, 485, kSentinel}}, + {{486, 487, kSentinel}}, + {{488, 489, kSentinel}}, + {{490, 491, kSentinel}}, // NOLINT + {{492, 493, kSentinel}}, + {{494, 495, kSentinel}}, + {{497, 498, 499, kSentinel}}, + {{500, 501, kSentinel}}, // NOLINT + {{504, 505, kSentinel}}, + {{506, 507, kSentinel}}, + {{508, 509, kSentinel}}, + {{510, 511, kSentinel}}, // NOLINT + {{512, 513, kSentinel}}, + {{514, 515, kSentinel}}, + {{516, 517, kSentinel}}, + {{518, 519, kSentinel}}, // NOLINT + {{520, 521, kSentinel}}, + {{522, 523, kSentinel}}, + {{524, 525, kSentinel}}, + {{526, 527, kSentinel}}, // NOLINT + {{528, 529, kSentinel}}, + {{530, 531, kSentinel}}, + {{532, 533, kSentinel}}, + {{534, 535, kSentinel}}, // NOLINT + {{536, 537, kSentinel}}, + {{538, 539, kSentinel}}, + {{540, 541, kSentinel}}, + {{542, 543, kSentinel}}, // NOLINT + {{546, 547, kSentinel}}, + {{548, 549, kSentinel}}, + {{550, 551, kSentinel}}, + {{552, 553, kSentinel}}, // NOLINT + {{554, 555, kSentinel}}, + {{556, 557, kSentinel}}, + {{558, 559, kSentinel}}, + {{560, 561, kSentinel}}, // NOLINT + {{562, 563, kSentinel}}, + {{570, 11365, kSentinel}}, + {{571, 572, kSentinel}}, + {{574, 11366, kSentinel}}, // NOLINT + {{575, 11390, kSentinel}}, + {{576, 11391, kSentinel}}, + {{577, 578, kSentinel}}, + {{580, 649, kSentinel}}, // NOLINT + {{581, 652, kSentinel}}, + {{582, 583, kSentinel}}, + {{584, 585, kSentinel}}, + {{586, 587, kSentinel}}, // NOLINT + {{588, 589, kSentinel}}, + {{590, 591, kSentinel}}, + {{592, 11375, kSentinel}}, + {{593, 11373, kSentinel}}, // NOLINT + {{594, 11376, kSentinel}}, + {{604, 42923, kSentinel}}, + {{609, 42924, kSentinel}}, + {{613, 42893, kSentinel}}, // NOLINT + {{614, 42922, kSentinel}}, + {{619, 11362, kSentinel}}, + {{620, 42925, kSentinel}}, + {{625, 11374, kSentinel}}, // NOLINT + {{637, 11364, kSentinel}}, + {{647, 42929, kSentinel}}, + {{670, 42928, kSentinel}}, + {{837, 921, 953, 8126}}, // NOLINT + {{880, 881, kSentinel}}, + {{882, 883, kSentinel}}, + {{886, 887, kSentinel}}, + {{891, 1021, kSentinel}}, // NOLINT + {{893, 1023, kSentinel}}, + {{895, 1011, kSentinel}}, + {{902, 940, kSentinel}}, + {{904, 941, kSentinel}}, // NOLINT + {{906, 943, kSentinel}}, + {{908, 972, kSentinel}}, + {{910, 973, kSentinel}}, + {{911, 974, kSentinel}}, // NOLINT + {{913, 945, kSentinel}}, + {{914, 946, 976, kSentinel}}, + {{915, 947, kSentinel}}, + {{916, 948, kSentinel}}, // NOLINT + {{917, 949, 1013, kSentinel}}, + {{918, 950, kSentinel}}, + {{919, 951, kSentinel}}, + {{920, 952, 977, kSentinel}}, // NOLINT + {{922, 954, 1008, kSentinel}}, + {{923, 955, kSentinel}}, + {{925, 957, kSentinel}}, + {{927, 959, kSentinel}}, // NOLINT + {{928, 960, 982, kSentinel}}, + {{929, 961, 1009, kSentinel}}, + {{931, 962, 963, kSentinel}}, + {{932, 964, kSentinel}}, // NOLINT + {{933, 965, kSentinel}}, + {{934, 966, 981, kSentinel}}, + {{935, 967, kSentinel}}, + {{939, 971, kSentinel}}, // NOLINT + {{975, 983, kSentinel}}, + {{984, 985, kSentinel}}, + {{986, 987, kSentinel}}, + {{988, 989, kSentinel}}, // NOLINT + {{990, 991, kSentinel}}, + {{992, 993, kSentinel}}, + {{994, 995, kSentinel}}, + {{996, 997, kSentinel}}, // NOLINT + {{998, 999, kSentinel}}, + {{1000, 1001, kSentinel}}, + {{1002, 1003, kSentinel}}, + {{1004, 1005, kSentinel}}, // NOLINT + {{1006, 1007, kSentinel}}, + {{1010, 1017, kSentinel}}, + {{1015, 1016, kSentinel}}, + {{1018, 1019, kSentinel}}, // NOLINT + {{1024, 1104, kSentinel}}, + {{1039, 1119, kSentinel}}, + {{1040, 1072, kSentinel}}, + {{1071, 1103, kSentinel}}, // NOLINT + {{1120, 1121, kSentinel}}, + {{1122, 1123, kSentinel}}, + {{1124, 1125, kSentinel}}, + {{1126, 1127, kSentinel}}, // NOLINT + {{1128, 1129, kSentinel}}, + {{1130, 1131, kSentinel}}, + {{1132, 1133, kSentinel}}, + {{1134, 1135, kSentinel}}, // NOLINT + {{1136, 1137, kSentinel}}, + {{1138, 1139, kSentinel}}, + {{1140, 1141, kSentinel}}, + {{1142, 1143, kSentinel}}, // NOLINT + {{1144, 1145, kSentinel}}, + {{1146, 1147, kSentinel}}, + {{1148, 1149, kSentinel}}, + {{1150, 1151, kSentinel}}, // NOLINT + {{1152, 1153, kSentinel}}, + {{1162, 1163, kSentinel}}, + {{1164, 1165, kSentinel}}, + {{1166, 1167, kSentinel}}, // NOLINT + {{1168, 1169, kSentinel}}, + {{1170, 1171, kSentinel}}, + {{1172, 1173, kSentinel}}, + {{1174, 1175, kSentinel}}, // NOLINT + {{1176, 1177, kSentinel}}, + {{1178, 1179, kSentinel}}, + {{1180, 1181, kSentinel}}, + {{1182, 1183, kSentinel}}, // NOLINT + {{1184, 1185, kSentinel}}, + {{1186, 1187, kSentinel}}, + {{1188, 1189, kSentinel}}, + {{1190, 1191, kSentinel}}, // NOLINT + {{1192, 1193, kSentinel}}, + {{1194, 1195, kSentinel}}, + {{1196, 1197, kSentinel}}, + {{1198, 1199, kSentinel}}, // NOLINT + {{1200, 1201, kSentinel}}, + {{1202, 1203, kSentinel}}, + {{1204, 1205, kSentinel}}, + {{1206, 1207, kSentinel}}, // NOLINT + {{1208, 1209, kSentinel}}, + {{1210, 1211, kSentinel}}, + {{1212, 1213, kSentinel}}, + {{1214, 1215, kSentinel}}, // NOLINT + {{1216, 1231, kSentinel}}, + {{1217, 1218, kSentinel}}, + {{1219, 1220, kSentinel}}, + {{1221, 1222, kSentinel}}, // NOLINT + {{1223, 1224, kSentinel}}, + {{1225, 1226, kSentinel}}, + {{1227, 1228, kSentinel}}, + {{1229, 1230, kSentinel}}, // NOLINT + {{1232, 1233, kSentinel}}, + {{1234, 1235, kSentinel}}, + {{1236, 1237, kSentinel}}, + {{1238, 1239, kSentinel}}, // NOLINT + {{1240, 1241, kSentinel}}, + {{1242, 1243, kSentinel}}, + {{1244, 1245, kSentinel}}, + {{1246, 1247, kSentinel}}, // NOLINT + {{1248, 1249, kSentinel}}, + {{1250, 1251, kSentinel}}, + {{1252, 1253, kSentinel}}, + {{1254, 1255, kSentinel}}, // NOLINT + {{1256, 1257, kSentinel}}, + {{1258, 1259, kSentinel}}, + {{1260, 1261, kSentinel}}, + {{1262, 1263, kSentinel}}, // NOLINT + {{1264, 1265, kSentinel}}, + {{1266, 1267, kSentinel}}, + {{1268, 1269, kSentinel}}, + {{1270, 1271, kSentinel}}, // NOLINT + {{1272, 1273, kSentinel}}, + {{1274, 1275, kSentinel}}, + {{1276, 1277, kSentinel}}, + {{1278, 1279, kSentinel}}, // NOLINT + {{1280, 1281, kSentinel}}, + {{1282, 1283, kSentinel}}, + {{1284, 1285, kSentinel}}, + {{1286, 1287, kSentinel}}, // NOLINT + {{1288, 1289, kSentinel}}, + {{1290, 1291, kSentinel}}, + {{1292, 1293, kSentinel}}, + {{1294, 1295, kSentinel}}, // NOLINT + {{1296, 1297, kSentinel}}, + {{1298, 1299, kSentinel}}, + {{1300, 1301, kSentinel}}, + {{1302, 1303, kSentinel}}, // NOLINT + {{1304, 1305, kSentinel}}, + {{1306, 1307, kSentinel}}, + {{1308, 1309, kSentinel}}, + {{1310, 1311, kSentinel}}, // NOLINT + {{1312, 1313, kSentinel}}, + {{1314, 1315, kSentinel}}, + {{1316, 1317, kSentinel}}, + {{1318, 1319, kSentinel}}, // NOLINT + {{1320, 1321, kSentinel}}, + {{1322, 1323, kSentinel}}, + {{1324, 1325, kSentinel}}, + {{1326, 1327, kSentinel}}, // NOLINT + {{1329, 1377, kSentinel}}, + {{1366, 1414, kSentinel}}, + {{4256, 11520, kSentinel}}, + {{4293, 11557, kSentinel}}, // NOLINT + {{4295, 11559, kSentinel}}, + {{4301, 11565, kSentinel}}, + {{7545, 42877, kSentinel}}, + {{7549, 11363, kSentinel}}, // NOLINT + {{7680, 7681, kSentinel}}, + {{7682, 7683, kSentinel}}, + {{7684, 7685, kSentinel}}, + {{7686, 7687, kSentinel}}, // NOLINT + {{7688, 7689, kSentinel}}, + {{7690, 7691, kSentinel}}, + {{7692, 7693, kSentinel}}, + {{7694, 7695, kSentinel}}, // NOLINT + {{7696, 7697, kSentinel}}, + {{7698, 7699, kSentinel}}, + {{7700, 7701, kSentinel}}, + {{7702, 7703, kSentinel}}, // NOLINT + {{7704, 7705, kSentinel}}, + {{7706, 7707, kSentinel}}, + {{7708, 7709, kSentinel}}, + {{7710, 7711, kSentinel}}, // NOLINT + {{7712, 7713, kSentinel}}, + {{7714, 7715, kSentinel}}, + {{7716, 7717, kSentinel}}, + {{7718, 7719, kSentinel}}, // NOLINT + {{7720, 7721, kSentinel}}, + {{7722, 7723, kSentinel}}, + {{7724, 7725, kSentinel}}, + {{7726, 7727, kSentinel}}, // NOLINT + {{7728, 7729, kSentinel}}, + {{7730, 7731, kSentinel}}, + {{7732, 7733, kSentinel}}, + {{7734, 7735, kSentinel}}, // NOLINT + {{7736, 7737, kSentinel}}, + {{7738, 7739, kSentinel}}, + {{7740, 7741, kSentinel}}, + {{7742, 7743, kSentinel}}, // NOLINT + {{7744, 7745, kSentinel}}, + {{7746, 7747, kSentinel}}, + {{7748, 7749, kSentinel}}, + {{7750, 7751, kSentinel}}, // NOLINT + {{7752, 7753, kSentinel}}, + {{7754, 7755, kSentinel}}, + {{7756, 7757, kSentinel}}, + {{7758, 7759, kSentinel}}, // NOLINT + {{7760, 7761, kSentinel}}, + {{7762, 7763, kSentinel}}, + {{7764, 7765, kSentinel}}, + {{7766, 7767, kSentinel}}, // NOLINT + {{7768, 7769, kSentinel}}, + {{7770, 7771, kSentinel}}, + {{7772, 7773, kSentinel}}, + {{7774, 7775, kSentinel}}, // NOLINT + {{7776, 7777, 7835, kSentinel}}, + {{7778, 7779, kSentinel}}, + {{7780, 7781, kSentinel}}, + {{7782, 7783, kSentinel}}, // NOLINT + {{7784, 7785, kSentinel}}, + {{7786, 7787, kSentinel}}, + {{7788, 7789, kSentinel}}, + {{7790, 7791, kSentinel}}, // NOLINT + {{7792, 7793, kSentinel}}, + {{7794, 7795, kSentinel}}, + {{7796, 7797, kSentinel}}, + {{7798, 7799, kSentinel}}, // NOLINT + {{7800, 7801, kSentinel}}, + {{7802, 7803, kSentinel}}, + {{7804, 7805, kSentinel}}, + {{7806, 7807, kSentinel}}, // NOLINT + {{7808, 7809, kSentinel}}, + {{7810, 7811, kSentinel}}, + {{7812, 7813, kSentinel}}, + {{7814, 7815, kSentinel}}, // NOLINT + {{7816, 7817, kSentinel}}, + {{7818, 7819, kSentinel}}, + {{7820, 7821, kSentinel}}, + {{7822, 7823, kSentinel}}, // NOLINT + {{7824, 7825, kSentinel}}, + {{7826, 7827, kSentinel}}, + {{7828, 7829, kSentinel}}, + {{7840, 7841, kSentinel}}, // NOLINT + {{7842, 7843, kSentinel}}, + {{7844, 7845, kSentinel}}, + {{7846, 7847, kSentinel}}, + {{7848, 7849, kSentinel}}, // NOLINT + {{7850, 7851, kSentinel}}, + {{7852, 7853, kSentinel}}, + {{7854, 7855, kSentinel}}, + {{7856, 7857, kSentinel}}, // NOLINT + {{7858, 7859, kSentinel}}, + {{7860, 7861, kSentinel}}, + {{7862, 7863, kSentinel}}, + {{7864, 7865, kSentinel}}, // NOLINT + {{7866, 7867, kSentinel}}, + {{7868, 7869, kSentinel}}, + {{7870, 7871, kSentinel}}, + {{7872, 7873, kSentinel}}, // NOLINT + {{7874, 7875, kSentinel}}, + {{7876, 7877, kSentinel}}, + {{7878, 7879, kSentinel}}, + {{7880, 7881, kSentinel}}, // NOLINT + {{7882, 7883, kSentinel}}, + {{7884, 7885, kSentinel}}, + {{7886, 7887, kSentinel}}, + {{7888, 7889, kSentinel}}, // NOLINT + {{7890, 7891, kSentinel}}, + {{7892, 7893, kSentinel}}, + {{7894, 7895, kSentinel}}, + {{7896, 7897, kSentinel}}, // NOLINT + {{7898, 7899, kSentinel}}, + {{7900, 7901, kSentinel}}, + {{7902, 7903, kSentinel}}, + {{7904, 7905, kSentinel}}, // NOLINT + {{7906, 7907, kSentinel}}, + {{7908, 7909, kSentinel}}, + {{7910, 7911, kSentinel}}, + {{7912, 7913, kSentinel}}, // NOLINT + {{7914, 7915, kSentinel}}, + {{7916, 7917, kSentinel}}, + {{7918, 7919, kSentinel}}, + {{7920, 7921, kSentinel}}, // NOLINT + {{7922, 7923, kSentinel}}, + {{7924, 7925, kSentinel}}, + {{7926, 7927, kSentinel}}, + {{7928, 7929, kSentinel}}, // NOLINT + {{7930, 7931, kSentinel}}, + {{7932, 7933, kSentinel}}, + {{7934, 7935, kSentinel}}, + {{7936, 7944, kSentinel}}, // NOLINT + {{7943, 7951, kSentinel}}, + {{7952, 7960, kSentinel}}, + {{7957, 7965, kSentinel}}, + {{7968, 7976, kSentinel}}, // NOLINT + {{7975, 7983, kSentinel}}, + {{7984, 7992, kSentinel}}, + {{7991, 7999, kSentinel}}, + {{8000, 8008, kSentinel}}, // NOLINT + {{8005, 8013, kSentinel}}, + {{8017, 8025, kSentinel}}, + {{8019, 8027, kSentinel}}, + {{8021, 8029, kSentinel}}, // NOLINT + {{8023, 8031, kSentinel}}, + {{8032, 8040, kSentinel}}, + {{8039, 8047, kSentinel}}, + {{8048, 8122, kSentinel}}, // NOLINT + {{8049, 8123, kSentinel}}, + {{8050, 8136, kSentinel}}, + {{8053, 8139, kSentinel}}, + {{8054, 8154, kSentinel}}, // NOLINT + {{8055, 8155, kSentinel}}, + {{8056, 8184, kSentinel}}, + {{8057, 8185, kSentinel}}, + {{8058, 8170, kSentinel}}, // NOLINT + {{8059, 8171, kSentinel}}, + {{8060, 8186, kSentinel}}, + {{8061, 8187, kSentinel}}, + {{8112, 8120, kSentinel}}, // NOLINT + {{8113, 8121, kSentinel}}, + {{8144, 8152, kSentinel}}, + {{8145, 8153, kSentinel}}, + {{8160, 8168, kSentinel}}, // NOLINT + {{8161, 8169, kSentinel}}, + {{8165, 8172, kSentinel}}, + {{kSentinel}}}; // NOLINT +static const uint16_t kEcma262UnCanonicalizeTable0Size = 1005; // NOLINT +static const int32_t kEcma262UnCanonicalizeTable0[2010] = { + 1073741889, 1, 90, 5, 1073741921, 1, + 122, 5, 181, 9, 1073742016, 13, + 214, 17, 1073742040, 21, // NOLINT + 222, 25, 1073742048, 13, 246, 17, + 1073742072, 21, 254, 25, 255, 29, + 256, 33, 257, 33, // NOLINT + 258, 37, 259, 37, 260, 41, + 261, 41, 262, 45, 263, 45, + 264, 49, 265, 49, // NOLINT + 266, 53, 267, 53, 268, 57, + 269, 57, 270, 61, 271, 61, + 272, 65, 273, 65, // NOLINT + 274, 69, 275, 69, 276, 73, + 277, 73, 278, 77, 279, 77, + 280, 81, 281, 81, // NOLINT + 282, 85, 283, 85, 284, 89, + 285, 89, 286, 93, 287, 93, + 288, 97, 289, 97, // NOLINT + 290, 101, 291, 101, 292, 105, + 293, 105, 294, 109, 295, 109, + 296, 113, 297, 113, // NOLINT + 298, 117, 299, 117, 300, 121, + 301, 121, 302, 125, 303, 125, + 306, 129, 307, 129, // NOLINT + 308, 133, 309, 133, 310, 137, + 311, 137, 313, 141, 314, 141, + 315, 145, 316, 145, // NOLINT + 317, 149, 318, 149, 319, 153, + 320, 153, 321, 157, 322, 157, + 323, 161, 324, 161, // NOLINT + 325, 165, 326, 165, 327, 169, + 328, 169, 330, 173, 331, 173, + 332, 177, 333, 177, // NOLINT + 334, 181, 335, 181, 336, 185, + 337, 185, 338, 189, 339, 189, + 340, 193, 341, 193, // NOLINT + 342, 197, 343, 197, 344, 201, + 345, 201, 346, 205, 347, 205, + 348, 209, 349, 209, // NOLINT + 350, 213, 351, 213, 352, 217, + 353, 217, 354, 221, 355, 221, + 356, 225, 357, 225, // NOLINT + 358, 229, 359, 229, 360, 233, + 361, 233, 362, 237, 363, 237, + 364, 241, 365, 241, // NOLINT + 366, 245, 367, 245, 368, 249, + 369, 249, 370, 253, 371, 253, + 372, 257, 373, 257, // NOLINT + 374, 261, 375, 261, 376, 29, + 377, 265, 378, 265, 379, 269, + 380, 269, 381, 273, // NOLINT + 382, 273, 384, 277, 385, 281, + 386, 285, 387, 285, 388, 289, + 389, 289, 390, 293, // NOLINT + 391, 297, 392, 297, 1073742217, 301, + 394, 305, 395, 309, 396, 309, + 398, 313, 399, 317, // NOLINT + 400, 321, 401, 325, 402, 325, + 403, 329, 404, 333, 405, 337, + 406, 341, 407, 345, // NOLINT + 408, 349, 409, 349, 410, 353, + 412, 357, 413, 361, 414, 365, + 415, 369, 416, 373, // NOLINT + 417, 373, 418, 377, 419, 377, + 420, 381, 421, 381, 422, 385, + 423, 389, 424, 389, // NOLINT + 425, 393, 428, 397, 429, 397, + 430, 401, 431, 405, 432, 405, + 1073742257, 409, 434, 413, // NOLINT + 435, 417, 436, 417, 437, 421, + 438, 421, 439, 425, 440, 429, + 441, 429, 444, 433, // NOLINT + 445, 433, 447, 437, 452, 441, + 453, 441, 454, 441, 455, 445, + 456, 445, 457, 445, // NOLINT + 458, 449, 459, 449, 460, 449, + 461, 453, 462, 453, 463, 457, + 464, 457, 465, 461, // NOLINT + 466, 461, 467, 465, 468, 465, + 469, 469, 470, 469, 471, 473, + 472, 473, 473, 477, // NOLINT + 474, 477, 475, 481, 476, 481, + 477, 313, 478, 485, 479, 485, + 480, 489, 481, 489, // NOLINT + 482, 493, 483, 493, 484, 497, + 485, 497, 486, 501, 487, 501, + 488, 505, 489, 505, // NOLINT + 490, 509, 491, 509, 492, 513, + 493, 513, 494, 517, 495, 517, + 497, 521, 498, 521, // NOLINT + 499, 521, 500, 525, 501, 525, + 502, 337, 503, 437, 504, 529, + 505, 529, 506, 533, // NOLINT + 507, 533, 508, 537, 509, 537, + 510, 541, 511, 541, 512, 545, + 513, 545, 514, 549, // NOLINT + 515, 549, 516, 553, 517, 553, + 518, 557, 519, 557, 520, 561, + 521, 561, 522, 565, // NOLINT + 523, 565, 524, 569, 525, 569, + 526, 573, 527, 573, 528, 577, + 529, 577, 530, 581, // NOLINT + 531, 581, 532, 585, 533, 585, + 534, 589, 535, 589, 536, 593, + 537, 593, 538, 597, // NOLINT + 539, 597, 540, 601, 541, 601, + 542, 605, 543, 605, 544, 365, + 546, 609, 547, 609, // NOLINT + 548, 613, 549, 613, 550, 617, + 551, 617, 552, 621, 553, 621, + 554, 625, 555, 625, // NOLINT + 556, 629, 557, 629, 558, 633, + 559, 633, 560, 637, 561, 637, + 562, 641, 563, 641, // NOLINT + 570, 645, 571, 649, 572, 649, + 573, 353, 574, 653, 1073742399, 657, + 576, 661, 577, 665, // NOLINT + 578, 665, 579, 277, 580, 669, + 581, 673, 582, 677, 583, 677, + 584, 681, 585, 681, // NOLINT + 586, 685, 587, 685, 588, 689, + 589, 689, 590, 693, 591, 693, + 592, 697, 593, 701, // NOLINT + 594, 705, 595, 281, 596, 293, + 1073742422, 301, 599, 305, 601, 317, + 603, 321, 604, 709, // NOLINT + 608, 329, 609, 713, 611, 333, + 613, 717, 614, 721, 616, 345, + 617, 341, 619, 725, // NOLINT + 620, 729, 623, 357, 625, 733, + 626, 361, 629, 369, 637, 737, + 640, 385, 643, 393, // NOLINT + 647, 741, 648, 401, 649, 669, + 1073742474, 409, 651, 413, 652, 673, + 658, 425, 670, 745, // NOLINT + 837, 749, 880, 753, 881, 753, + 882, 757, 883, 757, 886, 761, + 887, 761, 1073742715, 765, // NOLINT + 893, 769, 895, 773, 902, 777, + 1073742728, 781, 906, 785, 908, 789, + 1073742734, 793, 911, 797, // NOLINT + 913, 801, 914, 805, 1073742739, 809, + 916, 813, 917, 817, 1073742742, 821, + 919, 825, 920, 829, // NOLINT + 921, 749, 922, 833, 923, 837, + 924, 9, 1073742749, 841, 927, 845, + 928, 849, 929, 853, // NOLINT + 931, 857, 1073742756, 861, 933, 865, + 934, 869, 1073742759, 873, 939, 877, + 940, 777, 1073742765, 781, // NOLINT + 943, 785, 945, 801, 946, 805, + 1073742771, 809, 948, 813, 949, 817, + 1073742774, 821, 951, 825, // NOLINT + 952, 829, 953, 749, 954, 833, + 955, 837, 956, 9, 1073742781, 841, + 959, 845, 960, 849, // NOLINT + 961, 853, 962, 857, 963, 857, + 1073742788, 861, 965, 865, 966, 869, + 1073742791, 873, 971, 877, // NOLINT + 972, 789, 1073742797, 793, 974, 797, + 975, 881, 976, 805, 977, 829, + 981, 869, 982, 849, // NOLINT + 983, 881, 984, 885, 985, 885, + 986, 889, 987, 889, 988, 893, + 989, 893, 990, 897, // NOLINT + 991, 897, 992, 901, 993, 901, + 994, 905, 995, 905, 996, 909, + 997, 909, 998, 913, // NOLINT + 999, 913, 1000, 917, 1001, 917, + 1002, 921, 1003, 921, 1004, 925, + 1005, 925, 1006, 929, // NOLINT + 1007, 929, 1008, 833, 1009, 853, + 1010, 933, 1011, 773, 1013, 817, + 1015, 937, 1016, 937, // NOLINT + 1017, 933, 1018, 941, 1019, 941, + 1073742845, 765, 1023, 769, 1073742848, 945, + 1039, 949, 1073742864, 953, // NOLINT + 1071, 957, 1073742896, 953, 1103, 957, + 1073742928, 945, 1119, 949, 1120, 961, + 1121, 961, 1122, 965, // NOLINT + 1123, 965, 1124, 969, 1125, 969, + 1126, 973, 1127, 973, 1128, 977, + 1129, 977, 1130, 981, // NOLINT + 1131, 981, 1132, 985, 1133, 985, + 1134, 989, 1135, 989, 1136, 993, + 1137, 993, 1138, 997, // NOLINT + 1139, 997, 1140, 1001, 1141, 1001, + 1142, 1005, 1143, 1005, 1144, 1009, + 1145, 1009, 1146, 1013, // NOLINT + 1147, 1013, 1148, 1017, 1149, 1017, + 1150, 1021, 1151, 1021, 1152, 1025, + 1153, 1025, 1162, 1029, // NOLINT + 1163, 1029, 1164, 1033, 1165, 1033, + 1166, 1037, 1167, 1037, 1168, 1041, + 1169, 1041, 1170, 1045, // NOLINT + 1171, 1045, 1172, 1049, 1173, 1049, + 1174, 1053, 1175, 1053, 1176, 1057, + 1177, 1057, 1178, 1061, // NOLINT + 1179, 1061, 1180, 1065, 1181, 1065, + 1182, 1069, 1183, 1069, 1184, 1073, + 1185, 1073, 1186, 1077, // NOLINT + 1187, 1077, 1188, 1081, 1189, 1081, + 1190, 1085, 1191, 1085, 1192, 1089, + 1193, 1089, 1194, 1093, // NOLINT + 1195, 1093, 1196, 1097, 1197, 1097, + 1198, 1101, 1199, 1101, 1200, 1105, + 1201, 1105, 1202, 1109, // NOLINT + 1203, 1109, 1204, 1113, 1205, 1113, + 1206, 1117, 1207, 1117, 1208, 1121, + 1209, 1121, 1210, 1125, // NOLINT + 1211, 1125, 1212, 1129, 1213, 1129, + 1214, 1133, 1215, 1133, 1216, 1137, + 1217, 1141, 1218, 1141, // NOLINT + 1219, 1145, 1220, 1145, 1221, 1149, + 1222, 1149, 1223, 1153, 1224, 1153, + 1225, 1157, 1226, 1157, // NOLINT + 1227, 1161, 1228, 1161, 1229, 1165, + 1230, 1165, 1231, 1137, 1232, 1169, + 1233, 1169, 1234, 1173, // NOLINT + 1235, 1173, 1236, 1177, 1237, 1177, + 1238, 1181, 1239, 1181, 1240, 1185, + 1241, 1185, 1242, 1189, // NOLINT + 1243, 1189, 1244, 1193, 1245, 1193, + 1246, 1197, 1247, 1197, 1248, 1201, + 1249, 1201, 1250, 1205, // NOLINT + 1251, 1205, 1252, 1209, 1253, 1209, + 1254, 1213, 1255, 1213, 1256, 1217, + 1257, 1217, 1258, 1221, // NOLINT + 1259, 1221, 1260, 1225, 1261, 1225, + 1262, 1229, 1263, 1229, 1264, 1233, + 1265, 1233, 1266, 1237, // NOLINT + 1267, 1237, 1268, 1241, 1269, 1241, + 1270, 1245, 1271, 1245, 1272, 1249, + 1273, 1249, 1274, 1253, // NOLINT + 1275, 1253, 1276, 1257, 1277, 1257, + 1278, 1261, 1279, 1261, 1280, 1265, + 1281, 1265, 1282, 1269, // NOLINT + 1283, 1269, 1284, 1273, 1285, 1273, + 1286, 1277, 1287, 1277, 1288, 1281, + 1289, 1281, 1290, 1285, // NOLINT + 1291, 1285, 1292, 1289, 1293, 1289, + 1294, 1293, 1295, 1293, 1296, 1297, + 1297, 1297, 1298, 1301, // NOLINT + 1299, 1301, 1300, 1305, 1301, 1305, + 1302, 1309, 1303, 1309, 1304, 1313, + 1305, 1313, 1306, 1317, // NOLINT + 1307, 1317, 1308, 1321, 1309, 1321, + 1310, 1325, 1311, 1325, 1312, 1329, + 1313, 1329, 1314, 1333, // NOLINT + 1315, 1333, 1316, 1337, 1317, 1337, + 1318, 1341, 1319, 1341, 1320, 1345, + 1321, 1345, 1322, 1349, // NOLINT + 1323, 1349, 1324, 1353, 1325, 1353, + 1326, 1357, 1327, 1357, 1073743153, 1361, + 1366, 1365, 1073743201, 1361, // NOLINT + 1414, 1365, 1073746080, 1369, 4293, 1373, + 4295, 1377, 4301, 1381, 7545, 1385, + 7549, 1389, 7680, 1393, // NOLINT + 7681, 1393, 7682, 1397, 7683, 1397, + 7684, 1401, 7685, 1401, 7686, 1405, + 7687, 1405, 7688, 1409, // NOLINT + 7689, 1409, 7690, 1413, 7691, 1413, + 7692, 1417, 7693, 1417, 7694, 1421, + 7695, 1421, 7696, 1425, // NOLINT + 7697, 1425, 7698, 1429, 7699, 1429, + 7700, 1433, 7701, 1433, 7702, 1437, + 7703, 1437, 7704, 1441, // NOLINT + 7705, 1441, 7706, 1445, 7707, 1445, + 7708, 1449, 7709, 1449, 7710, 1453, + 7711, 1453, 7712, 1457, // NOLINT + 7713, 1457, 7714, 1461, 7715, 1461, + 7716, 1465, 7717, 1465, 7718, 1469, + 7719, 1469, 7720, 1473, // NOLINT + 7721, 1473, 7722, 1477, 7723, 1477, + 7724, 1481, 7725, 1481, 7726, 1485, + 7727, 1485, 7728, 1489, // NOLINT + 7729, 1489, 7730, 1493, 7731, 1493, + 7732, 1497, 7733, 1497, 7734, 1501, + 7735, 1501, 7736, 1505, // NOLINT + 7737, 1505, 7738, 1509, 7739, 1509, + 7740, 1513, 7741, 1513, 7742, 1517, + 7743, 1517, 7744, 1521, // NOLINT + 7745, 1521, 7746, 1525, 7747, 1525, + 7748, 1529, 7749, 1529, 7750, 1533, + 7751, 1533, 7752, 1537, // NOLINT + 7753, 1537, 7754, 1541, 7755, 1541, + 7756, 1545, 7757, 1545, 7758, 1549, + 7759, 1549, 7760, 1553, // NOLINT + 7761, 1553, 7762, 1557, 7763, 1557, + 7764, 1561, 7765, 1561, 7766, 1565, + 7767, 1565, 7768, 1569, // NOLINT + 7769, 1569, 7770, 1573, 7771, 1573, + 7772, 1577, 7773, 1577, 7774, 1581, + 7775, 1581, 7776, 1585, // NOLINT + 7777, 1585, 7778, 1589, 7779, 1589, + 7780, 1593, 7781, 1593, 7782, 1597, + 7783, 1597, 7784, 1601, // NOLINT + 7785, 1601, 7786, 1605, 7787, 1605, + 7788, 1609, 7789, 1609, 7790, 1613, + 7791, 1613, 7792, 1617, // NOLINT + 7793, 1617, 7794, 1621, 7795, 1621, + 7796, 1625, 7797, 1625, 7798, 1629, + 7799, 1629, 7800, 1633, // NOLINT + 7801, 1633, 7802, 1637, 7803, 1637, + 7804, 1641, 7805, 1641, 7806, 1645, + 7807, 1645, 7808, 1649, // NOLINT + 7809, 1649, 7810, 1653, 7811, 1653, + 7812, 1657, 7813, 1657, 7814, 1661, + 7815, 1661, 7816, 1665, // NOLINT + 7817, 1665, 7818, 1669, 7819, 1669, + 7820, 1673, 7821, 1673, 7822, 1677, + 7823, 1677, 7824, 1681, // NOLINT + 7825, 1681, 7826, 1685, 7827, 1685, + 7828, 1689, 7829, 1689, 7835, 1585, + 7840, 1693, 7841, 1693, // NOLINT + 7842, 1697, 7843, 1697, 7844, 1701, + 7845, 1701, 7846, 1705, 7847, 1705, + 7848, 1709, 7849, 1709, // NOLINT + 7850, 1713, 7851, 1713, 7852, 1717, + 7853, 1717, 7854, 1721, 7855, 1721, + 7856, 1725, 7857, 1725, // NOLINT + 7858, 1729, 7859, 1729, 7860, 1733, + 7861, 1733, 7862, 1737, 7863, 1737, + 7864, 1741, 7865, 1741, // NOLINT + 7866, 1745, 7867, 1745, 7868, 1749, + 7869, 1749, 7870, 1753, 7871, 1753, + 7872, 1757, 7873, 1757, // NOLINT + 7874, 1761, 7875, 1761, 7876, 1765, + 7877, 1765, 7878, 1769, 7879, 1769, + 7880, 1773, 7881, 1773, // NOLINT + 7882, 1777, 7883, 1777, 7884, 1781, + 7885, 1781, 7886, 1785, 7887, 1785, + 7888, 1789, 7889, 1789, // NOLINT + 7890, 1793, 7891, 1793, 7892, 1797, + 7893, 1797, 7894, 1801, 7895, 1801, + 7896, 1805, 7897, 1805, // NOLINT + 7898, 1809, 7899, 1809, 7900, 1813, + 7901, 1813, 7902, 1817, 7903, 1817, + 7904, 1821, 7905, 1821, // NOLINT + 7906, 1825, 7907, 1825, 7908, 1829, + 7909, 1829, 7910, 1833, 7911, 1833, + 7912, 1837, 7913, 1837, // NOLINT + 7914, 1841, 7915, 1841, 7916, 1845, + 7917, 1845, 7918, 1849, 7919, 1849, + 7920, 1853, 7921, 1853, // NOLINT + 7922, 1857, 7923, 1857, 7924, 1861, + 7925, 1861, 7926, 1865, 7927, 1865, + 7928, 1869, 7929, 1869, // NOLINT + 7930, 1873, 7931, 1873, 7932, 1877, + 7933, 1877, 7934, 1881, 7935, 1881, + 1073749760, 1885, 7943, 1889, // NOLINT + 1073749768, 1885, 7951, 1889, 1073749776, 1893, + 7957, 1897, 1073749784, 1893, 7965, 1897, + 1073749792, 1901, 7975, 1905, // NOLINT + 1073749800, 1901, 7983, 1905, 1073749808, 1909, + 7991, 1913, 1073749816, 1909, 7999, 1913, + 1073749824, 1917, 8005, 1921, // NOLINT + 1073749832, 1917, 8013, 1921, 8017, 1925, + 8019, 1929, 8021, 1933, 8023, 1937, + 8025, 1925, 8027, 1929, // NOLINT + 8029, 1933, 8031, 1937, 1073749856, 1941, + 8039, 1945, 1073749864, 1941, 8047, 1945, + 1073749872, 1949, 8049, 1953, // NOLINT + 1073749874, 1957, 8053, 1961, 1073749878, 1965, + 8055, 1969, 1073749880, 1973, 8057, 1977, + 1073749882, 1981, 8059, 1985, // NOLINT + 1073749884, 1989, 8061, 1993, 1073749936, 1997, + 8113, 2001, 1073749944, 1997, 8121, 2001, + 1073749946, 1949, 8123, 1953, // NOLINT + 8126, 749, 1073749960, 1957, 8139, 1961, + 1073749968, 2005, 8145, 2009, 1073749976, 2005, + 8153, 2009, 1073749978, 1965, // NOLINT + 8155, 1969, 1073749984, 2013, 8161, 2017, + 8165, 2021, 1073749992, 2013, 8169, 2017, + 1073749994, 1981, 8171, 1985, // NOLINT + 8172, 2021, 1073750008, 1973, 8185, 1977, + 1073750010, 1989, 8187, 1993}; // NOLINT +static const MultiCharacterSpecialCase<2> + kEcma262UnCanonicalizeMultiStrings1[83] = { // NOLINT + {{8498, 8526}}, {{8544, 8560}}, {{8559, 8575}}, + {{8579, 8580}}, // NOLINT + {{9398, 9424}}, {{9423, 9449}}, {{11264, 11312}}, + {{11310, 11358}}, // NOLINT + {{11360, 11361}}, {{619, 11362}}, {{7549, 11363}}, + {{637, 11364}}, // NOLINT + {{570, 11365}}, {{574, 11366}}, {{11367, 11368}}, + {{11369, 11370}}, // NOLINT + {{11371, 11372}}, {{593, 11373}}, {{625, 11374}}, + {{592, 11375}}, // NOLINT + {{594, 11376}}, {{11378, 11379}}, {{11381, 11382}}, + {{575, 11390}}, // NOLINT + {{576, 11391}}, {{11392, 11393}}, {{11394, 11395}}, + {{11396, 11397}}, // NOLINT + {{11398, 11399}}, {{11400, 11401}}, {{11402, 11403}}, + {{11404, 11405}}, // NOLINT + {{11406, 11407}}, {{11408, 11409}}, {{11410, 11411}}, + {{11412, 11413}}, // NOLINT + {{11414, 11415}}, {{11416, 11417}}, {{11418, 11419}}, + {{11420, 11421}}, // NOLINT + {{11422, 11423}}, {{11424, 11425}}, {{11426, 11427}}, + {{11428, 11429}}, // NOLINT + {{11430, 11431}}, {{11432, 11433}}, {{11434, 11435}}, + {{11436, 11437}}, // NOLINT + {{11438, 11439}}, {{11440, 11441}}, {{11442, 11443}}, + {{11444, 11445}}, // NOLINT + {{11446, 11447}}, {{11448, 11449}}, {{11450, 11451}}, + {{11452, 11453}}, // NOLINT + {{11454, 11455}}, {{11456, 11457}}, {{11458, 11459}}, + {{11460, 11461}}, // NOLINT + {{11462, 11463}}, {{11464, 11465}}, {{11466, 11467}}, + {{11468, 11469}}, // NOLINT + {{11470, 11471}}, {{11472, 11473}}, {{11474, 11475}}, + {{11476, 11477}}, // NOLINT + {{11478, 11479}}, {{11480, 11481}}, {{11482, 11483}}, + {{11484, 11485}}, // NOLINT + {{11486, 11487}}, {{11488, 11489}}, {{11490, 11491}}, + {{11499, 11500}}, // NOLINT + {{11501, 11502}}, {{11506, 11507}}, {{4256, 11520}}, + {{4293, 11557}}, // NOLINT + {{4295, 11559}}, {{4301, 11565}}, {{kSentinel}}}; // NOLINT +static const uint16_t kEcma262UnCanonicalizeTable1Size = 149; // NOLINT +static const int32_t kEcma262UnCanonicalizeTable1[298] = { + 306, 1, 334, 1, 1073742176, 5, 367, 9, + 1073742192, 5, 383, 9, 387, 13, 388, 13, // NOLINT + 1073743030, 17, 1231, 21, 1073743056, 17, 1257, 21, + 1073744896, 25, 3118, 29, 1073744944, 25, 3166, 29, // NOLINT + 3168, 33, 3169, 33, 3170, 37, 3171, 41, + 3172, 45, 3173, 49, 3174, 53, 3175, 57, // NOLINT + 3176, 57, 3177, 61, 3178, 61, 3179, 65, + 3180, 65, 3181, 69, 3182, 73, 3183, 77, // NOLINT + 3184, 81, 3186, 85, 3187, 85, 3189, 89, + 3190, 89, 1073745022, 93, 3199, 97, 3200, 101, // NOLINT + 3201, 101, 3202, 105, 3203, 105, 3204, 109, + 3205, 109, 3206, 113, 3207, 113, 3208, 117, // NOLINT + 3209, 117, 3210, 121, 3211, 121, 3212, 125, + 3213, 125, 3214, 129, 3215, 129, 3216, 133, // NOLINT + 3217, 133, 3218, 137, 3219, 137, 3220, 141, + 3221, 141, 3222, 145, 3223, 145, 3224, 149, // NOLINT + 3225, 149, 3226, 153, 3227, 153, 3228, 157, + 3229, 157, 3230, 161, 3231, 161, 3232, 165, // NOLINT + 3233, 165, 3234, 169, 3235, 169, 3236, 173, + 3237, 173, 3238, 177, 3239, 177, 3240, 181, // NOLINT + 3241, 181, 3242, 185, 3243, 185, 3244, 189, + 3245, 189, 3246, 193, 3247, 193, 3248, 197, // NOLINT + 3249, 197, 3250, 201, 3251, 201, 3252, 205, + 3253, 205, 3254, 209, 3255, 209, 3256, 213, // NOLINT + 3257, 213, 3258, 217, 3259, 217, 3260, 221, + 3261, 221, 3262, 225, 3263, 225, 3264, 229, // NOLINT + 3265, 229, 3266, 233, 3267, 233, 3268, 237, + 3269, 237, 3270, 241, 3271, 241, 3272, 245, // NOLINT + 3273, 245, 3274, 249, 3275, 249, 3276, 253, + 3277, 253, 3278, 257, 3279, 257, 3280, 261, // NOLINT + 3281, 261, 3282, 265, 3283, 265, 3284, 269, + 3285, 269, 3286, 273, 3287, 273, 3288, 277, // NOLINT + 3289, 277, 3290, 281, 3291, 281, 3292, 285, + 3293, 285, 3294, 289, 3295, 289, 3296, 293, // NOLINT + 3297, 293, 3298, 297, 3299, 297, 3307, 301, + 3308, 301, 3309, 305, 3310, 305, 3314, 309, // NOLINT + 3315, 309, 1073745152, 313, 3365, 317, 3367, 321, + 3373, 325}; // NOLINT +static const MultiCharacterSpecialCase<2> + kEcma262UnCanonicalizeMultiStrings5[104] = { // NOLINT + {{42560, 42561}}, {{42562, 42563}}, + {{42564, 42565}}, {{42566, 42567}}, // NOLINT + {{42568, 42569}}, {{42570, 42571}}, + {{42572, 42573}}, {{42574, 42575}}, // NOLINT + {{42576, 42577}}, {{42578, 42579}}, + {{42580, 42581}}, {{42582, 42583}}, // NOLINT + {{42584, 42585}}, {{42586, 42587}}, + {{42588, 42589}}, {{42590, 42591}}, // NOLINT + {{42592, 42593}}, {{42594, 42595}}, + {{42596, 42597}}, {{42598, 42599}}, // NOLINT + {{42600, 42601}}, {{42602, 42603}}, + {{42604, 42605}}, {{42624, 42625}}, // NOLINT + {{42626, 42627}}, {{42628, 42629}}, + {{42630, 42631}}, {{42632, 42633}}, // NOLINT + {{42634, 42635}}, {{42636, 42637}}, + {{42638, 42639}}, {{42640, 42641}}, // NOLINT + {{42642, 42643}}, {{42644, 42645}}, + {{42646, 42647}}, {{42648, 42649}}, // NOLINT + {{42650, 42651}}, {{42786, 42787}}, + {{42788, 42789}}, {{42790, 42791}}, // NOLINT + {{42792, 42793}}, {{42794, 42795}}, + {{42796, 42797}}, {{42798, 42799}}, // NOLINT + {{42802, 42803}}, {{42804, 42805}}, + {{42806, 42807}}, {{42808, 42809}}, // NOLINT + {{42810, 42811}}, {{42812, 42813}}, + {{42814, 42815}}, {{42816, 42817}}, // NOLINT + {{42818, 42819}}, {{42820, 42821}}, + {{42822, 42823}}, {{42824, 42825}}, // NOLINT + {{42826, 42827}}, {{42828, 42829}}, + {{42830, 42831}}, {{42832, 42833}}, // NOLINT + {{42834, 42835}}, {{42836, 42837}}, + {{42838, 42839}}, {{42840, 42841}}, // NOLINT + {{42842, 42843}}, {{42844, 42845}}, + {{42846, 42847}}, {{42848, 42849}}, // NOLINT + {{42850, 42851}}, {{42852, 42853}}, + {{42854, 42855}}, {{42856, 42857}}, // NOLINT + {{42858, 42859}}, {{42860, 42861}}, + {{42862, 42863}}, {{42873, 42874}}, // NOLINT + {{42875, 42876}}, {{7545, 42877}}, + {{42878, 42879}}, {{42880, 42881}}, // NOLINT + {{42882, 42883}}, {{42884, 42885}}, + {{42886, 42887}}, {{42891, 42892}}, // NOLINT + {{613, 42893}}, {{42896, 42897}}, + {{42898, 42899}}, {{42902, 42903}}, // NOLINT + {{42904, 42905}}, {{42906, 42907}}, + {{42908, 42909}}, {{42910, 42911}}, // NOLINT + {{42912, 42913}}, {{42914, 42915}}, + {{42916, 42917}}, {{42918, 42919}}, // NOLINT + {{42920, 42921}}, {{614, 42922}}, + {{604, 42923}}, {{609, 42924}}, // NOLINT + {{620, 42925}}, {{670, 42928}}, + {{647, 42929}}, {{kSentinel}}}; // NOLINT +static const uint16_t kEcma262UnCanonicalizeTable5Size = 198; // NOLINT +static const int32_t + kEcma262UnCanonicalizeTable5[396] = + {1600, 1, 1601, 1, 1602, 5, 1603, 5, + 1604, 9, 1605, 9, 1606, 13, 1607, 13, // NOLINT + 1608, 17, 1609, 17, 1610, 21, 1611, 21, + 1612, 25, 1613, 25, 1614, 29, 1615, 29, // NOLINT + 1616, 33, 1617, 33, 1618, 37, 1619, 37, + 1620, 41, 1621, 41, 1622, 45, 1623, 45, // NOLINT + 1624, 49, 1625, 49, 1626, 53, 1627, 53, + 1628, 57, 1629, 57, 1630, 61, 1631, 61, // NOLINT + 1632, 65, 1633, 65, 1634, 69, 1635, 69, + 1636, 73, 1637, 73, 1638, 77, 1639, 77, // NOLINT + 1640, 81, 1641, 81, 1642, 85, 1643, 85, + 1644, 89, 1645, 89, 1664, 93, 1665, 93, // NOLINT + 1666, 97, 1667, 97, 1668, 101, 1669, 101, + 1670, 105, 1671, 105, 1672, 109, 1673, 109, // NOLINT + 1674, 113, 1675, 113, 1676, 117, 1677, 117, + 1678, 121, 1679, 121, 1680, 125, 1681, 125, // NOLINT + 1682, 129, 1683, 129, 1684, 133, 1685, 133, + 1686, 137, 1687, 137, 1688, 141, 1689, 141, // NOLINT + 1690, 145, 1691, 145, 1826, 149, 1827, 149, + 1828, 153, 1829, 153, 1830, 157, 1831, 157, // NOLINT + 1832, 161, 1833, 161, 1834, 165, 1835, 165, + 1836, 169, 1837, 169, 1838, 173, 1839, 173, // NOLINT + 1842, 177, 1843, 177, 1844, 181, 1845, 181, + 1846, 185, 1847, 185, 1848, 189, 1849, 189, // NOLINT + 1850, 193, 1851, 193, 1852, 197, 1853, 197, + 1854, 201, 1855, 201, 1856, 205, 1857, 205, // NOLINT + 1858, 209, 1859, 209, 1860, 213, 1861, 213, + 1862, 217, 1863, 217, 1864, 221, 1865, 221, // NOLINT + 1866, 225, 1867, 225, 1868, 229, 1869, 229, + 1870, 233, 1871, 233, 1872, 237, 1873, 237, // NOLINT + 1874, 241, 1875, 241, 1876, 245, 1877, 245, + 1878, 249, 1879, 249, 1880, 253, 1881, 253, // NOLINT + 1882, 257, 1883, 257, 1884, 261, 1885, 261, + 1886, 265, 1887, 265, 1888, 269, 1889, 269, // NOLINT + 1890, 273, 1891, 273, 1892, 277, 1893, 277, + 1894, 281, 1895, 281, 1896, 285, 1897, 285, // NOLINT + 1898, 289, 1899, 289, 1900, 293, 1901, 293, + 1902, 297, 1903, 297, 1913, 301, 1914, 301, // NOLINT + 1915, 305, 1916, 305, 1917, 309, 1918, 313, + 1919, 313, 1920, 317, 1921, 317, 1922, 321, // NOLINT + 1923, 321, 1924, 325, 1925, 325, 1926, 329, + 1927, 329, 1931, 333, 1932, 333, 1933, 337, // NOLINT + 1936, 341, 1937, 341, 1938, 345, 1939, 345, + 1942, 349, 1943, 349, 1944, 353, 1945, 353, // NOLINT + 1946, 357, 1947, 357, 1948, 361, 1949, 361, + 1950, 365, 1951, 365, 1952, 369, 1953, 369, // NOLINT + 1954, 373, 1955, 373, 1956, 377, 1957, 377, + 1958, 381, 1959, 381, 1960, 385, 1961, 385, // NOLINT + 1962, 389, 1963, 393, 1964, 397, 1965, 401, + 1968, 405, 1969, 409}; // NOLINT +static const MultiCharacterSpecialCase<2> + kEcma262UnCanonicalizeMultiStrings7[3] = { // NOLINT + {{65313, 65345}}, + {{65338, 65370}}, + {{kSentinel}}}; // NOLINT +static const uint16_t kEcma262UnCanonicalizeTable7Size = 4; // NOLINT +static const int32_t kEcma262UnCanonicalizeTable7[8] = { + 1073749793, 1, 7994, 5, 1073749825, 1, 8026, 5}; // NOLINT +int Ecma262UnCanonicalize::Convert(uchar c, uchar n, uchar* result, + bool* allow_caching_ptr) { + int chunk_index = c >> 13; + switch (chunk_index) { + case 0: + return LookupMapping<true>( + kEcma262UnCanonicalizeTable0, kEcma262UnCanonicalizeTable0Size, + kEcma262UnCanonicalizeMultiStrings0, c, n, result, allow_caching_ptr); + case 1: + return LookupMapping<true>( + kEcma262UnCanonicalizeTable1, kEcma262UnCanonicalizeTable1Size, + kEcma262UnCanonicalizeMultiStrings1, c, n, result, allow_caching_ptr); + case 5: + return LookupMapping<true>( + kEcma262UnCanonicalizeTable5, kEcma262UnCanonicalizeTable5Size, + kEcma262UnCanonicalizeMultiStrings5, c, n, result, allow_caching_ptr); + case 7: + return LookupMapping<true>( + kEcma262UnCanonicalizeTable7, kEcma262UnCanonicalizeTable7Size, + kEcma262UnCanonicalizeMultiStrings7, c, n, result, allow_caching_ptr); + default: + return 0; + } +} + +static const MultiCharacterSpecialCase<1> + kCanonicalizationRangeMultiStrings0[1] = { // NOLINT + {{kSentinel}}}; // NOLINT +static const uint16_t kCanonicalizationRangeTable0Size = 70; // NOLINT +static const int32_t kCanonicalizationRangeTable0[140] = { + 1073741889, 100, 90, 0, 1073741921, 100, 122, 0, + 1073742016, 88, 214, 0, 1073742040, 24, 222, 0, // NOLINT + 1073742048, 88, 246, 0, 1073742072, 24, 254, 0, + 1073742715, 8, 893, 0, 1073742728, 8, 906, 0, // NOLINT + 1073742749, 8, 927, 0, 1073742759, 16, 939, 0, + 1073742765, 8, 943, 0, 1073742781, 8, 959, 0, // NOLINT + 1073742791, 16, 971, 0, 1073742845, 8, 1023, 0, + 1073742848, 60, 1039, 0, 1073742864, 124, 1071, 0, // NOLINT + 1073742896, 124, 1103, 0, 1073742928, 60, 1119, 0, + 1073743153, 148, 1366, 0, 1073743201, 148, 1414, 0, // NOLINT + 1073746080, 148, 4293, 0, 1073749760, 28, 7943, 0, + 1073749768, 28, 7951, 0, 1073749776, 20, 7957, 0, // NOLINT + 1073749784, 20, 7965, 0, 1073749792, 28, 7975, 0, + 1073749800, 28, 7983, 0, 1073749808, 28, 7991, 0, // NOLINT + 1073749816, 28, 7999, 0, 1073749824, 20, 8005, 0, + 1073749832, 20, 8013, 0, 1073749856, 28, 8039, 0, // NOLINT + 1073749864, 28, 8047, 0, 1073749874, 12, 8053, 0, + 1073749960, 12, 8139, 0}; // NOLINT +static const MultiCharacterSpecialCase<1> + kCanonicalizationRangeMultiStrings1[1] = { // NOLINT + {{kSentinel}}}; // NOLINT +static const uint16_t kCanonicalizationRangeTable1Size = 14; // NOLINT +static const int32_t kCanonicalizationRangeTable1[28] = { + 1073742176, 60, 367, 0, 1073742192, 60, 383, 0, + 1073743030, 100, 1231, 0, 1073743056, 100, 1257, 0, // NOLINT + 1073744896, 184, 3118, 0, 1073744944, 184, 3166, 0, + 1073745152, 148, 3365, 0}; // NOLINT +static const MultiCharacterSpecialCase<1> + kCanonicalizationRangeMultiStrings7[1] = { // NOLINT + {{kSentinel}}}; // NOLINT +static const uint16_t kCanonicalizationRangeTable7Size = 4; // NOLINT +static const int32_t kCanonicalizationRangeTable7[8] = { + 1073749793, 100, 7994, 0, 1073749825, 100, 8026, 0}; // NOLINT +int CanonicalizationRange::Convert(uchar c, uchar n, uchar* result, + bool* allow_caching_ptr) { + int chunk_index = c >> 13; + switch (chunk_index) { + case 0: + return LookupMapping<false>( + kCanonicalizationRangeTable0, kCanonicalizationRangeTable0Size, + kCanonicalizationRangeMultiStrings0, c, n, result, allow_caching_ptr); + case 1: + return LookupMapping<false>( + kCanonicalizationRangeTable1, kCanonicalizationRangeTable1Size, + kCanonicalizationRangeMultiStrings1, c, n, result, allow_caching_ptr); + case 7: + return LookupMapping<false>( + kCanonicalizationRangeTable7, kCanonicalizationRangeTable7Size, + kCanonicalizationRangeMultiStrings7, c, n, result, allow_caching_ptr); + default: + return 0; + } +} + +#endif // !V8_INTL_SUPPORT + +} // namespace unibrow +} // namespace v8 diff --git a/js/src/irregexp/util/VectorShim.h b/js/src/irregexp/util/VectorShim.h new file mode 100644 index 0000000000..1b8882f234 --- /dev/null +++ b/js/src/irregexp/util/VectorShim.h @@ -0,0 +1,231 @@ +// Copyright 2014 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. + +#ifndef V8_UTIL_VECTOR_H_ +#define V8_UTIL_VECTOR_H_ + +#include <algorithm> +#include <cstring> +#include <iterator> +#include <memory> + +#include "js/AllocPolicy.h" +#include "js/Utility.h" +#include "js/Vector.h" + +namespace v8 { +namespace internal { + +////////////////////////////////////////////////// + +// Adapted from: +// https://github.com/v8/v8/blob/5f69bbc233c2d1baf149faf869a7901603929914/src/utils/allocation.h#L36-L58 + +template <typename T> +T* NewArray(size_t size) { + static_assert(std::is_pod<T>::value, ""); + js::AutoEnterOOMUnsafeRegion oomUnsafe; + T* result = static_cast<T*>(js_malloc(size * sizeof(T))); + if (!result) { + oomUnsafe.crash("Irregexp NewArray"); + } + return result; +} + +template <typename T> +void DeleteArray(T* array) { + js_free(array); +} + +} // namespace internal + +namespace base { + +////////////////////////////////////////////////// + +// A non-resizable vector containing a pointer and a length. +// The Vector may or may not own the pointer, depending on context. +// Origin: +// https://github.com/v8/v8/blob/5f69bbc233c2d1baf149faf869a7901603929914/src/utils/vector.h#L20-L134 + +template <typename T> +class Vector { + public: + constexpr Vector() : start_(nullptr), length_(0) {} + + constexpr Vector(T* data, size_t length) : start_(data), length_(length) { + MOZ_ASSERT_IF(length != 0, data != nullptr); + } + + static Vector<T> New(size_t length) { + return Vector<T>(v8::internal::NewArray<T>(length), length); + } + + // Returns a vector using the same backing storage as this one, + // spanning from and including 'from', to but not including 'to'. + Vector<T> SubVector(size_t from, size_t to) const { + MOZ_ASSERT(from <= to); + MOZ_ASSERT(to <= length_); + return Vector<T>(begin() + from, to - from); + } + + // Returns the length of the vector. Only use this if you really need an + // integer return value. Use {size()} otherwise. + int length() const { + MOZ_ASSERT(length_ <= static_cast<size_t>(std::numeric_limits<int>::max())); + return static_cast<int>(length_); + } + + // Returns the length of the vector as a size_t. + constexpr size_t size() const { return length_; } + + // Returns whether or not the vector is empty. + constexpr bool empty() const { return length_ == 0; } + + // Access individual vector elements - checks bounds in debug mode. + T& operator[](size_t index) const { + MOZ_ASSERT(index < length_); + return start_[index]; + } + + const T& at(size_t index) const { return operator[](index); } + + T& first() { return start_[0]; } + + T& last() { + MOZ_ASSERT(length_ > 0); + return start_[length_ - 1]; + } + + // Returns a pointer to the start of the data in the vector. + constexpr T* begin() const { return start_; } + + // Returns a pointer past the end of the data in the vector. + constexpr T* end() const { return start_ + length_; } + + // Returns a clone of this vector with a new backing store. + Vector<T> Clone() const { + T* result = v8::internal::NewArray<T>(length_); + for (size_t i = 0; i < length_; i++) result[i] = start_[i]; + return Vector<T>(result, length_); + } + + void Truncate(size_t length) { + MOZ_ASSERT(length <= length_); + length_ = length; + } + + // Releases the array underlying this vector. Once disposed the + // vector is empty. + void Dispose() { + DeleteArray(start_); + start_ = nullptr; + length_ = 0; + } + + Vector<T> operator+(size_t offset) const { + MOZ_ASSERT(offset <= length_); + return Vector<T>(start_ + offset, length_ - offset); + } + + Vector<T> operator+=(size_t offset) { + MOZ_ASSERT(offset <= length_); + start_ += offset; + length_ -= offset; + return *this; + } + + // Implicit conversion from Vector<T> to Vector<const T>. + inline operator Vector<const T>() const { + return Vector<const T>::cast(*this); + } + + template <typename S> + static constexpr Vector<T> cast(Vector<S> input) { + return Vector<T>(reinterpret_cast<T*>(input.begin()), + input.length() * sizeof(S) / sizeof(T)); + } + + bool operator==(const Vector<const T> other) const { + if (length_ != other.length_) return false; + if (start_ == other.start_) return true; + for (size_t i = 0; i < length_; ++i) { + if (start_[i] != other.start_[i]) { + return false; + } + } + return true; + } + + private: + T* start_; + size_t length_; +}; + +// The resulting vector does not contain a null-termination byte. If you want +// the null byte, use ArrayVector("foo"). +inline Vector<const char> CStrVector(const char* data) { + return Vector<const char>(data, strlen(data)); +} + +// Construct a Vector from a start pointer and a size. +template <typename T> +inline constexpr Vector<T> VectorOf(T* start, size_t size) { + return {start, size}; +} + +class DefaultAllocator { + public: + using Policy = js::SystemAllocPolicy; + Policy policy() const { return js::SystemAllocPolicy(); } +}; + +// SmallVector uses inline storage first, and reallocates when full. +// It is basically equivalent to js::Vector, and is implemented +// as a thin wrapper. +// V8's implementation: +// https://github.com/v8/v8/blob/main/src/base/small-vector.h +template <typename T, size_t kSize, typename Allocator = DefaultAllocator> +class SmallVector { + public: + explicit SmallVector(const Allocator& allocator = DefaultAllocator()) + : inner_(allocator.policy()) {} + SmallVector(size_t size) { resize_no_init(size); } + + inline bool empty() const { return inner_.empty(); } + inline const T& back() const { return inner_.back(); } + inline void pop_back() { inner_.popBack(); }; + template <typename... Args> + inline void emplace_back(Args&&... args) { + js::AutoEnterOOMUnsafeRegion oomUnsafe; + if (!inner_.emplaceBack(args...)) { + oomUnsafe.crash("Irregexp SmallVector emplace_back"); + } + }; + inline size_t size() const { return inner_.length(); } + inline const T& at(size_t index) const { return inner_[index]; } + T* data() { return inner_.begin(); } + T* begin() { return inner_.begin(); } + + T& operator[](size_t index) { return inner_[index]; } + const T& operator[](size_t index) const { return inner_[index]; } + + inline void clear() { inner_.clear(); } + + void resize_no_init(size_t new_size) { + js::AutoEnterOOMUnsafeRegion oomUnsafe; + if (!inner_.resizeUninitialized(new_size)) { + oomUnsafe.crash("Irregexp SmallVector resize"); + } + } + + private: + js::Vector<T, kSize, typename Allocator::Policy> inner_; +}; + +} // namespace base + +} // namespace v8 + +#endif // V8_UTIL_VECTOR_H_ diff --git a/js/src/irregexp/util/ZoneShim.h b/js/src/irregexp/util/ZoneShim.h new file mode 100644 index 0000000000..f5dd33b8aa --- /dev/null +++ b/js/src/irregexp/util/ZoneShim.h @@ -0,0 +1,417 @@ +// 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. + +#ifndef V8_UTIL_ZONE_H_ +#define V8_UTIL_ZONE_H_ + +#include <list> +#include <map> +#include <set> +#include <unordered_map> +#include <vector> + +#include "ds/LifoAlloc.h" +#include "ds/Sort.h" +#include "irregexp/util/VectorShim.h" + +namespace v8 { +namespace internal { + +// V8::Zone ~= LifoAlloc +class Zone { + public: + Zone(js::LifoAlloc& alloc) : lifoAlloc_(alloc) {} + + template <typename T, typename... Args> + T* New(Args&&... args) { + js::LifoAlloc::AutoFallibleScope fallible(&lifoAlloc_); + js::AutoEnterOOMUnsafeRegion oomUnsafe; + void* memory = lifoAlloc_.alloc(sizeof(T)); + if (!memory) { + oomUnsafe.crash("Irregexp Zone::New"); + } + return new (memory) T(std::forward<Args>(args)...); + } + + // Allocates uninitialized memory for 'length' number of T instances. + template <typename T> + T* NewArray(size_t length) { + js::LifoAlloc::AutoFallibleScope fallible(&lifoAlloc_); + js::AutoEnterOOMUnsafeRegion oomUnsafe; + void* memory = lifoAlloc_.alloc(length * sizeof(T)); + if (!memory) { + oomUnsafe.crash("Irregexp Zone::New"); + } + return static_cast<T*>(memory); + } + + void DeleteAll() { lifoAlloc_.freeAll(); } + + // Returns true if the total memory allocated exceeds a threshold. + static const size_t kExcessLimit = 256 * 1024 * 1024; + bool excess_allocation() const { + return lifoAlloc_.computedSizeOfExcludingThis() > kExcessLimit; + } + + js::LifoAlloc& inner() { return lifoAlloc_; } + + private: + js::LifoAlloc& lifoAlloc_; +}; + +// Superclass for classes allocated in a Zone. +// Based on: https://github.com/v8/v8/blob/master/src/zone/zone.h +class ZoneObject { + public: + // new (zone) SomeObject(...) was the old pattern. + // Delete the constructor to avoid using it accidentally. + void* operator new(size_t size, Zone* zone) = delete; + + // Allow non-allocating placement new + void* operator new(size_t size, void* ptr) { return ptr; } + + // Ideally, the delete operator should be private instead of + // public, but unfortunately the compiler sometimes synthesizes + // (unused) destructors for classes derived from ZoneObject, which + // require the operator to be visible. MSVC requires the delete + // operator to be public. + + // ZoneObjects should never be deleted individually; use + // Zone::DeleteAll() to delete all zone objects in one go. + void operator delete(void*, size_t) { MOZ_CRASH("unreachable"); } + void operator delete(void* pointer, Zone* zone) { MOZ_CRASH("unreachable"); } +}; + +// ZoneLists are growable lists with constant-time access to the +// elements. The list itself and all its elements are allocated in the +// Zone. ZoneLists cannot be deleted individually; you can delete all +// objects in the Zone by calling Zone::DeleteAll(). +// Used throughout irregexp. +// Based on: https://github.com/v8/v8/blob/master/src/zone/zone-list.h +template <typename T> +class ZoneList final : public ZoneObject { + public: + // Construct a new ZoneList with the given capacity; the length is + // always zero. The capacity must be non-negative. + ZoneList(int capacity, Zone* zone) : capacity_(capacity) { + data_ = (capacity_ > 0) ? zone->NewArray<T>(capacity_) : nullptr; + } + // Construct a new ZoneList by copying the elements of the given ZoneList. + ZoneList(const ZoneList<T>& other, Zone* zone) + : ZoneList(other.length(), zone) { + AddAll(other, zone); + } + + // Construct a new ZoneList by copying the elements of the given vector. + ZoneList(const base::Vector<const T>& other, Zone* zone) + : ZoneList(other.length(), zone) { + AddAll(other, zone); + } + + ZoneList(ZoneList<T>&& other) { *this = std::move(other); } + + ZoneList& operator=(ZoneList&& other) { + MOZ_ASSERT(!data_); + data_ = other.data_; + capacity_ = other.capacity_; + length_ = other.length_; + other.Clear(); + return *this; + } + + // Returns a reference to the element at index i. This reference is not safe + // to use after operations that can change the list's backing store + // (e.g. Add). + inline T& operator[](int i) const { + MOZ_ASSERT(i >= 0); + MOZ_ASSERT(static_cast<unsigned>(i) < static_cast<unsigned>(length_)); + return data_[i]; + } + inline T& at(int i) const { return operator[](i); } + inline T& last() const { return at(length_ - 1); } + inline T& first() const { return at(0); } + + using iterator = T*; + inline iterator begin() const { return &data_[0]; } + inline iterator end() const { return &data_[length_]; } + + inline bool is_empty() const { return length_ == 0; } + inline int length() const { return length_; } + inline int capacity() const { return capacity_; } + + base::Vector<T> ToVector() const { return base::Vector<T>(data_, length_); } + base::Vector<T> ToVector(int start, int length) const { + return base::Vector<T>(data_ + start, std::min(length_ - start, length)); + } + + base::Vector<const T> ToConstVector() const { + return base::Vector<const T>(data_, length_); + } + + // Adds a copy of the given 'element' to the end of the list, + // expanding the list if necessary. + void Add(const T& element, Zone* zone) { + if (length_ < capacity_) { + data_[length_++] = element; + } else { + ZoneList<T>::ResizeAdd(element, zone); + } + } + // Add all the elements from the argument list to this list. + void AddAll(const ZoneList<T>& other, Zone* zone) { + AddAll(other.ToVector(), zone); + } + // Add all the elements from the vector to this list. + void AddAll(const base::Vector<const T>& other, Zone* zone) { + int result_length = length_ + other.length(); + if (capacity_ < result_length) { + Resize(result_length, zone); + } + if (std::is_fundamental<T>()) { + memcpy(data_ + length_, other.begin(), sizeof(*data_) * other.length()); + } else { + for (int i = 0; i < other.length(); i++) { + data_[length_ + i] = other.at(i); + } + } + length_ = result_length; + } + + // Overwrites the element at the specific index. + void Set(int index, const T& element) { + MOZ_ASSERT(index >= 0 && index <= length_); + data_[index] = element; + } + + // Removes the i'th element without deleting it even if T is a + // pointer type; moves all elements above i "down". Returns the + // removed element. This function's complexity is linear in the + // size of the list. + T Remove(int i) { + T element = at(i); + length_--; + while (i < length_) { + data_[i] = data_[i + 1]; + i++; + } + return element; + } + + // Removes the last element without deleting it even if T is a + // pointer type. Returns the removed element. + inline T RemoveLast() { return Remove(length_ - 1); } + + // Clears the list, setting the capacity and length to 0. + inline void Clear() { + data_ = nullptr; + capacity_ = 0; + length_ = 0; + } + + // Drops all but the first 'pos' elements from the list. + inline void Rewind(int pos) { + MOZ_ASSERT(0 <= pos && pos <= length_); + length_ = pos; + } + + inline bool Contains(const T& elm) const { + for (int i = 0; i < length_; i++) { + if (data_[i] == elm) return true; + } + return false; + } + + template <typename CompareFunction> + void StableSort(CompareFunction cmp, size_t start, size_t length) { + js::AutoEnterOOMUnsafeRegion oomUnsafe; + T* scratch = static_cast<T*>(js_malloc(length * sizeof(T))); + if (!scratch) { + oomUnsafe.crash("Irregexp stable sort scratch space"); + } + auto comparator = [cmp](const T& a, const T& b, bool* lessOrEqual) { + *lessOrEqual = cmp(&a, &b) <= 0; + return true; + }; + MOZ_ALWAYS_TRUE( + js::MergeSort(begin() + start, length, scratch, comparator)); + js_free(scratch); + } + + void operator delete(void* pointer) { MOZ_CRASH("unreachable"); } + void operator delete(void* pointer, Zone* zone) { MOZ_CRASH("unreachable"); } + + private: + T* data_ = nullptr; + int capacity_ = 0; + int length_ = 0; + + // Increase the capacity of a full list, and add an element. + // List must be full already. + void ResizeAdd(const T& element, Zone* zone) { + MOZ_ASSERT(length_ >= capacity_); + // Grow the list capacity by 100%, but make sure to let it grow + // even when the capacity is zero (possible initial case). + int new_capacity = 1 + 2 * capacity_; + // Since the element reference could be an element of the list, copy + // it out of the old backing storage before resizing. + T temp = element; + Resize(new_capacity, zone); + data_[length_++] = temp; + } + + // Resize the list. + void Resize(int new_capacity, Zone* zone) { + MOZ_ASSERT(length_ <= new_capacity); + static_assert(std::is_trivially_copyable<T>::value); + T* new_data = zone->NewArray<T>(new_capacity); + if (length_ > 0) { + memcpy(new_data, data_, length_ * sizeof(T)); + } + data_ = new_data; + capacity_ = new_capacity; + } + + ZoneList& operator=(const ZoneList&) = delete; + ZoneList() = delete; + ZoneList(const ZoneList&) = delete; +}; + +// Based on: https://github.com/v8/v8/blob/master/src/zone/zone-allocator.h +template <typename T> +class ZoneAllocator { + public: + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using value_type = T; + using size_type = size_t; + using difference_type = ptrdiff_t; + template <class O> + struct rebind { + using other = ZoneAllocator<O>; + }; + + explicit ZoneAllocator(Zone* zone) : zone_(zone) {} + template <typename U> + ZoneAllocator(const ZoneAllocator<U>& other) + : ZoneAllocator<T>(other.zone_) {} + template <typename U> + friend class ZoneAllocator; + + T* allocate(size_t n) { return zone_->NewArray<T>(n); } + void deallocate(T* p, size_t) {} // noop for zones + + bool operator==(ZoneAllocator const& other) const { + return zone_ == other.zone_; + } + bool operator!=(ZoneAllocator const& other) const { + return zone_ != other.zone_; + } + + using Policy = js::LifoAllocPolicy<js::Fallible>; + Policy policy() const { + return js::LifoAllocPolicy<js::Fallible>(zone_->inner()); + } + + private: + Zone* zone_; +}; + +// Zone wrappers for std containers: +// Origin: +// https://github.com/v8/v8/blob/5e514a969376dc63517d575b062758efd36cd757/src/zone/zone-containers.h#L25-L169 + +// A wrapper subclass for std::vector to make it easy to construct one +// that uses a zone allocator. +// Used throughout irregexp +template <typename T> +class ZoneVector : public std::vector<T, ZoneAllocator<T>> { + public: + ZoneVector(Zone* zone) + : std::vector<T, ZoneAllocator<T>>(ZoneAllocator<T>(zone)) {} + + // Constructs a new vector and fills it with {size} elements, each + // constructed via the default constructor. + ZoneVector(size_t size, Zone* zone) + : std::vector<T, ZoneAllocator<T>>(size, T(), ZoneAllocator<T>(zone)) {} + + // Constructs a new vector and fills it with the contents of the range + // [first, last). + template <class Iter> + ZoneVector(Iter first, Iter last, Zone* zone) + : std::vector<T, ZoneAllocator<T>>(first, last, ZoneAllocator<T>(zone)) {} +}; + +// A wrapper subclass for std::list to make it easy to construct one +// that uses a zone allocator. +// Used in regexp-bytecode-peephole.cc +template <typename T> +class ZoneLinkedList : public std::list<T, ZoneAllocator<T>> { + public: + // Constructs an empty list. + explicit ZoneLinkedList(Zone* zone) + : std::list<T, ZoneAllocator<T>>(ZoneAllocator<T>(zone)) {} +}; + +// A wrapper subclass for std::set to make it easy to construct one that uses +// a zone allocator. +// Used in regexp-parser.cc +template <typename K, typename Compare = std::less<K>> +class ZoneSet : public std::set<K, Compare, ZoneAllocator<K>> { + public: + // Constructs an empty set. + explicit ZoneSet(Zone* zone) + : std::set<K, Compare, ZoneAllocator<K>>(Compare(), + ZoneAllocator<K>(zone)) {} +}; + +// A wrapper subclass for std::map to make it easy to construct one that uses +// a zone allocator. +// Used in regexp-bytecode-peephole.cc +template <typename K, typename V, typename Compare = std::less<K>> +class ZoneMap + : public std::map<K, V, Compare, ZoneAllocator<std::pair<const K, V>>> { + public: + // Constructs an empty map. + explicit ZoneMap(Zone* zone) + : std::map<K, V, Compare, ZoneAllocator<std::pair<const K, V>>>( + Compare(), ZoneAllocator<std::pair<const K, V>>(zone)) {} +}; + +// A wrapper subclass for std::unordered_map to make it easy to construct one +// that uses a zone allocator. +// Used in regexp-bytecode-peephole.cc +template <typename K, typename V, typename Hash = std::hash<K>, + typename KeyEqual = std::equal_to<K>> +class ZoneUnorderedMap + : public std::unordered_map<K, V, Hash, KeyEqual, + ZoneAllocator<std::pair<const K, V>>> { + public: + // Constructs an empty map. + explicit ZoneUnorderedMap(Zone* zone, size_t bucket_count = 100) + : std::unordered_map<K, V, Hash, KeyEqual, + ZoneAllocator<std::pair<const K, V>>>( + bucket_count, Hash(), KeyEqual(), + ZoneAllocator<std::pair<const K, V>>(zone)) {} +}; + +// A wrapper subclass for base::SmallVector to make it easy to construct one +// that uses a zone allocator. +template <typename T, size_t kSize> +class SmallZoneVector : public base::SmallVector<T, kSize, ZoneAllocator<T>> { + public: + // Constructs an empty small vector. + explicit SmallZoneVector(Zone* zone) + : base::SmallVector<T, kSize, ZoneAllocator<T>>(ZoneAllocator<T>(zone)) {} + + explicit SmallZoneVector(size_t size, Zone* zone) + : base::SmallVector<T, kSize, ZoneAllocator<T>>( + size, ZoneAllocator<T>(ZoneAllocator<T>(zone))) {} +}; + +} // namespace internal +} // namespace v8 + +#endif // V8_UTIL_FLAG_H_ |