summaryrefslogtreecommitdiffstats
path: root/js/src/irregexp/RegExpAPI.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/irregexp/RegExpAPI.cpp')
-rw-r--r--js/src/irregexp/RegExpAPI.cpp922
1 files changed, 922 insertions, 0 deletions
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