summaryrefslogtreecommitdiffstats
path: root/js/src/vm/RegExpObject.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/vm/RegExpObject.cpp')
-rw-r--r--js/src/vm/RegExpObject.cpp1555
1 files changed, 1555 insertions, 0 deletions
diff --git a/js/src/vm/RegExpObject.cpp b/js/src/vm/RegExpObject.cpp
new file mode 100644
index 000000000..97f1163aa
--- /dev/null
+++ b/js/src/vm/RegExpObject.cpp
@@ -0,0 +1,1555 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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 "vm/RegExpObject.h"
+
+#include "mozilla/MemoryReporting.h"
+#include "mozilla/PodOperations.h"
+
+#include "jsstr.h"
+#ifdef DEBUG
+#include "jsutil.h"
+#endif
+
+#include "builtin/RegExp.h"
+#include "frontend/TokenStream.h"
+#ifdef DEBUG
+#include "irregexp/RegExpBytecode.h"
+#endif
+#include "irregexp/RegExpParser.h"
+#include "vm/MatchPairs.h"
+#include "vm/RegExpStatics.h"
+#include "vm/StringBuffer.h"
+#include "vm/TraceLogging.h"
+#ifdef DEBUG
+#include "vm/Unicode.h"
+#endif
+#include "vm/Xdr.h"
+
+#include "jsobjinlines.h"
+
+#include "vm/NativeObject-inl.h"
+#include "vm/Shape-inl.h"
+
+using namespace js;
+
+using mozilla::ArrayLength;
+using mozilla::DebugOnly;
+using mozilla::Maybe;
+using mozilla::PodCopy;
+using js::frontend::TokenStream;
+
+using JS::AutoCheckCannotGC;
+
+JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD);
+JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB);
+JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE);
+JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY);
+JS_STATIC_ASSERT(UnicodeFlag == JSREG_UNICODE);
+
+RegExpObject*
+js::RegExpAlloc(ExclusiveContext* cx, HandleObject proto /* = nullptr */)
+{
+ // Note: RegExp objects are always allocated in the tenured heap. This is
+ // not strictly required, but simplifies embedding them in jitcode.
+ Rooted<RegExpObject*> regexp(cx);
+
+ regexp = NewObjectWithClassProto<RegExpObject>(cx, proto, TenuredObject);
+ if (!regexp)
+ return nullptr;
+
+ regexp->initPrivate(nullptr);
+
+ if (!EmptyShape::ensureInitialCustomShape<RegExpObject>(cx, regexp))
+ return nullptr;
+
+ MOZ_ASSERT(regexp->lookupPure(cx->names().lastIndex)->slot() ==
+ RegExpObject::lastIndexSlot());
+
+ return regexp;
+}
+
+/* MatchPairs */
+
+bool
+MatchPairs::initArrayFrom(MatchPairs& copyFrom)
+{
+ MOZ_ASSERT(copyFrom.pairCount() > 0);
+
+ if (!allocOrExpandArray(copyFrom.pairCount()))
+ return false;
+
+ PodCopy(pairs_, copyFrom.pairs_, pairCount_);
+
+ return true;
+}
+
+bool
+ScopedMatchPairs::allocOrExpandArray(size_t pairCount)
+{
+ /* Array expansion is forbidden, but array reuse is acceptable. */
+ if (pairCount_) {
+ MOZ_ASSERT(pairs_);
+ MOZ_ASSERT(pairCount_ == pairCount);
+ return true;
+ }
+
+ MOZ_ASSERT(!pairs_);
+ pairs_ = (MatchPair*)lifoScope_.alloc().alloc(sizeof(MatchPair) * pairCount);
+ if (!pairs_)
+ return false;
+
+ pairCount_ = pairCount;
+ return true;
+}
+
+bool
+VectorMatchPairs::allocOrExpandArray(size_t pairCount)
+{
+ if (!vec_.resizeUninitialized(sizeof(MatchPair) * pairCount))
+ return false;
+
+ pairs_ = &vec_[0];
+ pairCount_ = pairCount;
+ return true;
+}
+
+/* RegExpObject */
+
+static inline void
+RegExpSharedReadBarrier(JSContext* cx, RegExpShared* shared)
+{
+ Zone* zone = cx->zone();
+ if (zone->needsIncrementalBarrier())
+ shared->trace(zone->barrierTracer());
+ if (shared->isMarkedGray())
+ shared->unmarkGray();
+}
+
+bool
+RegExpObject::getShared(JSContext* cx, RegExpGuard* g)
+{
+ if (RegExpShared* shared = maybeShared()) {
+ // Fetching a RegExpShared from an object requires a read
+ // barrier, as the shared pointer might be weak.
+ RegExpSharedReadBarrier(cx, shared);
+
+ g->init(*shared);
+ return true;
+ }
+
+ return createShared(cx, g);
+}
+
+/* static */ bool
+RegExpObject::isOriginalFlagGetter(JSNative native, RegExpFlag* mask)
+{
+ if (native == regexp_global) {
+ *mask = GlobalFlag;
+ return true;
+ }
+ if (native == regexp_ignoreCase) {
+ *mask = IgnoreCaseFlag;
+ return true;
+ }
+ if (native == regexp_multiline) {
+ *mask = MultilineFlag;
+ return true;
+ }
+ if (native == regexp_sticky) {
+ *mask = StickyFlag;
+ return true;
+ }
+ if (native == regexp_unicode) {
+ *mask = UnicodeFlag;
+ return true;
+ }
+
+ return false;
+}
+
+/* static */ void
+RegExpObject::trace(JSTracer* trc, JSObject* obj)
+{
+ RegExpShared* shared = obj->as<RegExpObject>().maybeShared();
+ if (!shared)
+ return;
+
+ // When tracing through the object normally, we have the option of
+ // unlinking the object from its RegExpShared so that the RegExpShared may
+ // be collected. To detect this we need to test all the following
+ // conditions, since:
+ // 1. During TraceRuntime, isHeapBusy() is true, but the tracer might not
+ // be a marking tracer.
+ // 2. When a write barrier executes, IsMarkingTracer is true, but
+ // isHeapBusy() will be false.
+ if (trc->runtime()->isHeapCollecting() &&
+ trc->isMarkingTracer() &&
+ !obj->asTenured().zone()->isPreservingCode())
+ {
+ obj->as<RegExpObject>().NativeObject::setPrivate(nullptr);
+ } else {
+ shared->trace(trc);
+ }
+}
+
+static const ClassOps RegExpObjectClassOps = {
+ nullptr, /* addProperty */
+ nullptr, /* delProperty */
+ nullptr, /* getProperty */
+ nullptr, /* setProperty */
+ nullptr, /* enumerate */
+ nullptr, /* resolve */
+ nullptr, /* mayResolve */
+ nullptr, /* finalize */
+ nullptr, /* call */
+ nullptr, /* hasInstance */
+ nullptr, /* construct */
+ RegExpObject::trace,
+};
+
+static const ClassSpec RegExpObjectClassSpec = {
+ GenericCreateConstructor<js::regexp_construct, 2, gc::AllocKind::FUNCTION>,
+ CreateRegExpPrototype,
+ nullptr,
+ js::regexp_static_props,
+ js::regexp_methods,
+ js::regexp_properties
+};
+
+const Class RegExpObject::class_ = {
+ js_RegExp_str,
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) |
+ JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp),
+ &RegExpObjectClassOps,
+ &RegExpObjectClassSpec
+};
+
+RegExpObject*
+RegExpObject::create(ExclusiveContext* cx, const char16_t* chars, size_t length, RegExpFlag flags,
+ TokenStream* tokenStream, LifoAlloc& alloc)
+{
+ RootedAtom source(cx, AtomizeChars(cx, chars, length));
+ if (!source)
+ return nullptr;
+
+ return create(cx, source, flags, tokenStream, alloc);
+}
+
+RegExpObject*
+RegExpObject::create(ExclusiveContext* cx, HandleAtom source, RegExpFlag flags,
+ TokenStream* tokenStream, LifoAlloc& alloc)
+{
+ Maybe<CompileOptions> dummyOptions;
+ Maybe<TokenStream> dummyTokenStream;
+ if (!tokenStream) {
+ dummyOptions.emplace(cx->asJSContext());
+ dummyTokenStream.emplace(cx, *dummyOptions,
+ (const char16_t*) nullptr, 0,
+ (frontend::StrictModeGetter*) nullptr);
+ tokenStream = dummyTokenStream.ptr();
+ }
+
+ if (!irregexp::ParsePatternSyntax(*tokenStream, alloc, source, flags & UnicodeFlag))
+ return nullptr;
+
+ Rooted<RegExpObject*> regexp(cx, RegExpAlloc(cx));
+ if (!regexp)
+ return nullptr;
+
+ regexp->initAndZeroLastIndex(source, flags, cx);
+
+ return regexp;
+}
+
+bool
+RegExpObject::createShared(JSContext* cx, RegExpGuard* g)
+{
+ Rooted<RegExpObject*> self(cx, this);
+
+ MOZ_ASSERT(!maybeShared());
+ if (!cx->compartment()->regExps.get(cx, getSource(), getFlags(), g))
+ return false;
+
+ self->setShared(**g);
+ return true;
+}
+
+Shape*
+RegExpObject::assignInitialShape(ExclusiveContext* cx, Handle<RegExpObject*> self)
+{
+ MOZ_ASSERT(self->empty());
+
+ JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0);
+
+ /* The lastIndex property alone is writable but non-configurable. */
+ return self->addDataProperty(cx, cx->names().lastIndex, LAST_INDEX_SLOT, JSPROP_PERMANENT);
+}
+
+void
+RegExpObject::initIgnoringLastIndex(HandleAtom source, RegExpFlag flags)
+{
+ // If this is a re-initialization with an existing RegExpShared, 'flags'
+ // may not match getShared()->flags, so forget the RegExpShared.
+ NativeObject::setPrivate(nullptr);
+
+ setSource(source);
+ setFlags(flags);
+}
+
+void
+RegExpObject::initAndZeroLastIndex(HandleAtom source, RegExpFlag flags, ExclusiveContext* cx)
+{
+ initIgnoringLastIndex(source, flags);
+ zeroLastIndex(cx);
+}
+
+static MOZ_ALWAYS_INLINE bool
+IsLineTerminator(const JS::Latin1Char c)
+{
+ return c == '\n' || c == '\r';
+}
+
+static MOZ_ALWAYS_INLINE bool
+IsLineTerminator(const char16_t c)
+{
+ return c == '\n' || c == '\r' || c == 0x2028 || c == 0x2029;
+}
+
+static MOZ_ALWAYS_INLINE bool
+AppendEscapedLineTerminator(StringBuffer& sb, const JS::Latin1Char c)
+{
+ switch (c) {
+ case '\n':
+ if (!sb.append('n'))
+ return false;
+ break;
+ case '\r':
+ if (!sb.append('r'))
+ return false;
+ break;
+ default:
+ MOZ_CRASH("Bad LineTerminator");
+ }
+ return true;
+}
+
+static MOZ_ALWAYS_INLINE bool
+AppendEscapedLineTerminator(StringBuffer& sb, const char16_t c)
+{
+ switch (c) {
+ case '\n':
+ if (!sb.append('n'))
+ return false;
+ break;
+ case '\r':
+ if (!sb.append('r'))
+ return false;
+ break;
+ case 0x2028:
+ if (!sb.append("u2028"))
+ return false;
+ break;
+ case 0x2029:
+ if (!sb.append("u2029"))
+ return false;
+ break;
+ default:
+ MOZ_CRASH("Bad LineTerminator");
+ }
+ return true;
+}
+
+template <typename CharT>
+static MOZ_ALWAYS_INLINE bool
+SetupBuffer(StringBuffer& sb, const CharT* oldChars, size_t oldLen, const CharT* it)
+{
+ if (mozilla::IsSame<CharT, char16_t>::value && !sb.ensureTwoByteChars())
+ return false;
+
+ if (!sb.reserve(oldLen + 1))
+ return false;
+
+ sb.infallibleAppend(oldChars, size_t(it - oldChars));
+ return true;
+}
+
+// Note: leaves the string buffer empty if no escaping need be performed.
+template <typename CharT>
+static bool
+EscapeRegExpPattern(StringBuffer& sb, const CharT* oldChars, size_t oldLen)
+{
+ bool inBrackets = false;
+ bool previousCharacterWasBackslash = false;
+
+ for (const CharT* it = oldChars; it < oldChars + oldLen; ++it) {
+ CharT ch = *it;
+ if (!previousCharacterWasBackslash) {
+ if (inBrackets) {
+ if (ch == ']')
+ inBrackets = false;
+ } else if (ch == '/') {
+ // There's a forward slash that needs escaping.
+ if (sb.empty()) {
+ // This is the first char we've seen that needs escaping,
+ // copy everything up to this point.
+ if (!SetupBuffer(sb, oldChars, oldLen, it))
+ return false;
+ }
+ if (!sb.append('\\'))
+ return false;
+ } else if (ch == '[') {
+ inBrackets = true;
+ }
+ }
+
+ if (IsLineTerminator(ch)) {
+ // There's LineTerminator that needs escaping.
+ if (sb.empty()) {
+ // This is the first char we've seen that needs escaping,
+ // copy everything up to this point.
+ if (!SetupBuffer(sb, oldChars, oldLen, it))
+ return false;
+ }
+ if (!previousCharacterWasBackslash) {
+ if (!sb.append('\\'))
+ return false;
+ }
+ if (!AppendEscapedLineTerminator(sb, ch))
+ return false;
+ } else if (!sb.empty()) {
+ if (!sb.append(ch))
+ return false;
+ }
+
+ if (previousCharacterWasBackslash)
+ previousCharacterWasBackslash = false;
+ else if (ch == '\\')
+ previousCharacterWasBackslash = true;
+ }
+
+ return true;
+}
+
+// ES6 draft rev32 21.2.3.2.4.
+JSAtom*
+js::EscapeRegExpPattern(JSContext* cx, HandleAtom src)
+{
+ // Step 2.
+ if (src->length() == 0)
+ return cx->names().emptyRegExp;
+
+ // We may never need to use |sb|. Start using it lazily.
+ StringBuffer sb(cx);
+
+ if (src->hasLatin1Chars()) {
+ JS::AutoCheckCannotGC nogc;
+ if (!::EscapeRegExpPattern(sb, src->latin1Chars(nogc), src->length()))
+ return nullptr;
+ } else {
+ JS::AutoCheckCannotGC nogc;
+ if (!::EscapeRegExpPattern(sb, src->twoByteChars(nogc), src->length()))
+ return nullptr;
+ }
+
+ // Step 3.
+ return sb.empty() ? src : sb.finishAtom();
+}
+
+// ES6 draft rev32 21.2.5.14. Optimized for RegExpObject.
+JSFlatString*
+RegExpObject::toString(JSContext* cx) const
+{
+ // Steps 3-4.
+ RootedAtom src(cx, getSource());
+ if (!src)
+ return nullptr;
+ RootedAtom escapedSrc(cx, EscapeRegExpPattern(cx, src));
+
+ // Step 7.
+ StringBuffer sb(cx);
+ size_t len = escapedSrc->length();
+ if (!sb.reserve(len + 2))
+ return nullptr;
+ sb.infallibleAppend('/');
+ if (!sb.append(escapedSrc))
+ return nullptr;
+ sb.infallibleAppend('/');
+
+ // Steps 5-7.
+ if (global() && !sb.append('g'))
+ return nullptr;
+ if (ignoreCase() && !sb.append('i'))
+ return nullptr;
+ if (multiline() && !sb.append('m'))
+ return nullptr;
+ if (unicode() && !sb.append('u'))
+ return nullptr;
+ if (sticky() && !sb.append('y'))
+ return nullptr;
+
+ return sb.finishString();
+}
+
+#ifdef DEBUG
+bool
+RegExpShared::dumpBytecode(JSContext* cx, bool match_only, HandleLinearString input)
+{
+ CompilationMode mode = match_only ? MatchOnly : Normal;
+ if (!compileIfNecessary(cx, input, mode, ForceByteCode))
+ return false;
+
+ const uint8_t* byteCode = compilation(mode, input->hasLatin1Chars()).byteCode;
+ const uint8_t* pc = byteCode;
+
+ auto Load32Aligned = [](const uint8_t* pc) -> int32_t {
+ MOZ_ASSERT((reinterpret_cast<uintptr_t>(pc) & 3) == 0);
+ return *reinterpret_cast<const int32_t*>(pc);
+ };
+
+ auto Load16Aligned = [](const uint8_t* pc) -> int32_t {
+ MOZ_ASSERT((reinterpret_cast<uintptr_t>(pc) & 1) == 0);
+ return *reinterpret_cast<const uint16_t*>(pc);
+ };
+
+ int32_t numRegisters = Load32Aligned(pc);
+ fprintf(stderr, "numRegisters: %d\n", numRegisters);
+ pc += 4;
+
+ fprintf(stderr, "loc op\n");
+ fprintf(stderr, "----- --\n");
+
+ auto DumpLower = [](const char* text) {
+ while (*text) {
+ fprintf(stderr, "%c", unicode::ToLowerCase(*text));
+ text++;
+ }
+ };
+
+#define BYTECODE(NAME) \
+ case irregexp::BC_##NAME: \
+ DumpLower(#NAME);
+#define ADVANCE(NAME) \
+ fprintf(stderr, "\n"); \
+ pc += irregexp::BC_##NAME##_LENGTH; \
+ maxPc = js::Max(maxPc, pc); \
+ break;
+#define STOP(NAME) \
+ fprintf(stderr, "\n"); \
+ pc += irregexp::BC_##NAME##_LENGTH; \
+ break;
+#define JUMP(NAME, OFFSET) \
+ fprintf(stderr, "\n"); \
+ maxPc = js::Max(maxPc, byteCode + OFFSET); \
+ pc += irregexp::BC_##NAME##_LENGTH; \
+ break;
+#define BRANCH(NAME, OFFSET) \
+ fprintf(stderr, "\n"); \
+ pc += irregexp::BC_##NAME##_LENGTH; \
+ maxPc = js::Max(maxPc, js::Max(pc, byteCode + OFFSET)); \
+ break;
+
+ // Bytecode has no end marker, we need to calculate the bytecode length by
+ // tracing jumps and branches.
+ const uint8_t* maxPc = pc;
+ while (pc <= maxPc) {
+ fprintf(stderr, "%05d: ", int32_t(pc - byteCode));
+ int32_t insn = Load32Aligned(pc);
+ switch (insn & irregexp::BYTECODE_MASK) {
+ BYTECODE(BREAK) {
+ STOP(BREAK);
+ }
+ BYTECODE(PUSH_CP) {
+ ADVANCE(PUSH_CP);
+ }
+ BYTECODE(PUSH_BT) {
+ int32_t offset = Load32Aligned(pc + 4);
+ fprintf(stderr, " %d",
+ offset);
+ // Pushed value is used by POP_BT for jumping.
+ // Resolve maxPc here.
+ BRANCH(PUSH_BT, offset);
+ }
+ BYTECODE(PUSH_REGISTER) {
+ fprintf(stderr, " reg[%d]",
+ insn >> irregexp::BYTECODE_SHIFT);
+ ADVANCE(PUSH_REGISTER);
+ }
+ BYTECODE(SET_REGISTER) {
+ fprintf(stderr, " reg[%d], %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ Load32Aligned(pc + 4));
+ ADVANCE(SET_REGISTER);
+ }
+ BYTECODE(ADVANCE_REGISTER) {
+ fprintf(stderr, " reg[%d], %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ Load32Aligned(pc + 4));
+ ADVANCE(ADVANCE_REGISTER);
+ }
+ BYTECODE(SET_REGISTER_TO_CP) {
+ fprintf(stderr, " reg[%d], %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ Load32Aligned(pc + 4));
+ ADVANCE(SET_REGISTER_TO_CP);
+ }
+ BYTECODE(SET_CP_TO_REGISTER) {
+ fprintf(stderr, " reg[%d]",
+ insn >> irregexp::BYTECODE_SHIFT);
+ ADVANCE(SET_CP_TO_REGISTER);
+ }
+ BYTECODE(SET_REGISTER_TO_SP) {
+ fprintf(stderr, " reg[%d]",
+ insn >> irregexp::BYTECODE_SHIFT);
+ ADVANCE(SET_REGISTER_TO_SP);
+ }
+ BYTECODE(SET_SP_TO_REGISTER) {
+ fprintf(stderr, " reg[%d]",
+ insn >> irregexp::BYTECODE_SHIFT);
+ ADVANCE(SET_SP_TO_REGISTER);
+ }
+ BYTECODE(POP_CP) {
+ ADVANCE(POP_CP);
+ }
+ BYTECODE(POP_BT) {
+ // Jump is already resolved in PUSH_BT.
+ STOP(POP_BT);
+ }
+ BYTECODE(POP_REGISTER) {
+ fprintf(stderr, " reg[%d]",
+ insn >> irregexp::BYTECODE_SHIFT);
+ ADVANCE(POP_REGISTER);
+ }
+ BYTECODE(FAIL) {
+ ADVANCE(FAIL);
+ }
+ BYTECODE(SUCCEED) {
+ ADVANCE(SUCCEED);
+ }
+ BYTECODE(ADVANCE_CP) {
+ fprintf(stderr, " %d",
+ insn >> irregexp::BYTECODE_SHIFT);
+ ADVANCE(ADVANCE_CP);
+ }
+ BYTECODE(GOTO) {
+ int32_t offset = Load32Aligned(pc + 4);
+ fprintf(stderr, " %d",
+ offset);
+ JUMP(GOTO, offset);
+ }
+ BYTECODE(ADVANCE_CP_AND_GOTO) {
+ int32_t offset = Load32Aligned(pc + 4);
+ fprintf(stderr, " %d, %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ offset);
+ JUMP(ADVANCE_CP_AND_GOTO, offset);
+ }
+ BYTECODE(CHECK_GREEDY) {
+ int32_t offset = Load32Aligned(pc + 4);
+ fprintf(stderr, " %d",
+ offset);
+ BRANCH(CHECK_GREEDY, offset);
+ }
+ BYTECODE(LOAD_CURRENT_CHAR) {
+ int32_t offset = Load32Aligned(pc + 4);
+ fprintf(stderr, " %d, %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ offset);
+ BRANCH(LOAD_CURRENT_CHAR, offset);
+ }
+ BYTECODE(LOAD_CURRENT_CHAR_UNCHECKED) {
+ fprintf(stderr, " %d",
+ insn >> irregexp::BYTECODE_SHIFT);
+ ADVANCE(LOAD_CURRENT_CHAR_UNCHECKED);
+ }
+ BYTECODE(LOAD_2_CURRENT_CHARS) {
+ int32_t offset = Load32Aligned(pc + 4);
+ fprintf(stderr, " %d, %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ offset);
+ BRANCH(LOAD_2_CURRENT_CHARS, offset);
+ }
+ BYTECODE(LOAD_2_CURRENT_CHARS_UNCHECKED) {
+ fprintf(stderr, " %d",
+ insn >> irregexp::BYTECODE_SHIFT);
+ ADVANCE(LOAD_2_CURRENT_CHARS_UNCHECKED);
+ }
+ BYTECODE(LOAD_4_CURRENT_CHARS) {
+ ADVANCE(LOAD_4_CURRENT_CHARS);
+ }
+ BYTECODE(LOAD_4_CURRENT_CHARS_UNCHECKED) {
+ ADVANCE(LOAD_4_CURRENT_CHARS_UNCHECKED);
+ }
+ BYTECODE(CHECK_4_CHARS) {
+ int32_t offset = Load32Aligned(pc + 8);
+ fprintf(stderr, " %d, %d",
+ Load32Aligned(pc + 4),
+ offset);
+ BRANCH(CHECK_4_CHARS, offset);
+ }
+ BYTECODE(CHECK_CHAR) {
+ int32_t offset = Load32Aligned(pc + 4);
+ fprintf(stderr, " %d, %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ offset);
+ BRANCH(CHECK_CHAR, offset);
+ }
+ BYTECODE(CHECK_NOT_4_CHARS) {
+ int32_t offset = Load32Aligned(pc + 8);
+ fprintf(stderr, " %d, %d",
+ Load32Aligned(pc + 4),
+ offset);
+ BRANCH(CHECK_NOT_4_CHARS, offset);
+ }
+ BYTECODE(CHECK_NOT_CHAR) {
+ int32_t offset = Load32Aligned(pc + 4);
+ fprintf(stderr, " %d, %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ offset);
+ BRANCH(CHECK_NOT_CHAR, offset);
+ }
+ BYTECODE(AND_CHECK_4_CHARS) {
+ int32_t offset = Load32Aligned(pc + 12);
+ fprintf(stderr, " %d, %d, %d",
+ Load32Aligned(pc + 4),
+ Load32Aligned(pc + 8),
+ offset);
+ BRANCH(AND_CHECK_4_CHARS, offset);
+ }
+ BYTECODE(AND_CHECK_CHAR) {
+ int32_t offset = Load32Aligned(pc + 8);
+ fprintf(stderr, " %d, %d, %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ Load32Aligned(pc + 4),
+ offset);
+ BRANCH(AND_CHECK_CHAR, offset);
+ }
+ BYTECODE(AND_CHECK_NOT_4_CHARS) {
+ int32_t offset = Load32Aligned(pc + 12);
+ fprintf(stderr, " %d, %d, %d",
+ Load32Aligned(pc + 4),
+ Load32Aligned(pc + 8),
+ offset);
+ BRANCH(AND_CHECK_NOT_4_CHARS, offset);
+ }
+ BYTECODE(AND_CHECK_NOT_CHAR) {
+ int32_t offset = Load32Aligned(pc + 8);
+ fprintf(stderr, " %d, %d, %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ Load32Aligned(pc + 4),
+ offset);
+ BRANCH(AND_CHECK_NOT_CHAR, offset);
+ }
+ BYTECODE(MINUS_AND_CHECK_NOT_CHAR) {
+ int32_t offset = Load32Aligned(pc + 8);
+ fprintf(stderr, " %d, %d, %d, %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ Load16Aligned(pc + 4),
+ Load16Aligned(pc + 6),
+ offset);
+ BRANCH(MINUS_AND_CHECK_NOT_CHAR, offset);
+ }
+ BYTECODE(CHECK_CHAR_IN_RANGE) {
+ int32_t offset = Load32Aligned(pc + 8);
+ fprintf(stderr, " %d, %d, %d",
+ Load16Aligned(pc + 4),
+ Load16Aligned(pc + 6),
+ offset);
+ BRANCH(CHECK_CHAR_IN_RANGE, offset);
+ }
+ BYTECODE(CHECK_CHAR_NOT_IN_RANGE) {
+ int32_t offset = Load32Aligned(pc + 8);
+ fprintf(stderr, " %d, %d, %d",
+ Load16Aligned(pc + 4),
+ Load16Aligned(pc + 6),
+ offset);
+ BRANCH(CHECK_CHAR_NOT_IN_RANGE, offset);
+ }
+ BYTECODE(CHECK_BIT_IN_TABLE) {
+ int32_t offset = Load32Aligned(pc + 4);
+ fprintf(stderr, " %d, "
+ "%02x %02x %02x %02x %02x %02x %02x %02x "
+ "%02x %02x %02x %02x %02x %02x %02x %02x",
+ offset,
+ pc[8], pc[9], pc[10], pc[11],
+ pc[12], pc[13], pc[14], pc[15],
+ pc[16], pc[17], pc[18], pc[19],
+ pc[20], pc[21], pc[22], pc[23]);
+ BRANCH(CHECK_BIT_IN_TABLE, offset);
+ }
+ BYTECODE(CHECK_LT) {
+ int32_t offset = Load32Aligned(pc + 4);
+ fprintf(stderr, " %d, %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ offset);
+ BRANCH(CHECK_LT, offset);
+ }
+ BYTECODE(CHECK_GT) {
+ int32_t offset = Load32Aligned(pc + 4);
+ fprintf(stderr, " %d, %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ offset);
+ BRANCH(CHECK_GT, offset);
+ }
+ BYTECODE(CHECK_REGISTER_LT) {
+ int32_t offset = Load32Aligned(pc + 8);
+ fprintf(stderr, " reg[%d], %d, %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ Load32Aligned(pc + 4),
+ offset);
+ BRANCH(CHECK_REGISTER_LT, offset);
+ }
+ BYTECODE(CHECK_REGISTER_GE) {
+ int32_t offset = Load32Aligned(pc + 8);
+ fprintf(stderr, " reg[%d], %d, %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ Load32Aligned(pc + 4),
+ offset);
+ BRANCH(CHECK_REGISTER_GE, offset);
+ }
+ BYTECODE(CHECK_REGISTER_EQ_POS) {
+ int32_t offset = Load32Aligned(pc + 4);
+ fprintf(stderr, " reg[%d], %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ offset);
+ BRANCH(CHECK_REGISTER_EQ_POS, offset);
+ }
+ BYTECODE(CHECK_NOT_REGS_EQUAL) {
+ int32_t offset = Load32Aligned(pc + 8);
+ fprintf(stderr, " reg[%d], %d, %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ Load32Aligned(pc + 4),
+ offset);
+ BRANCH(CHECK_NOT_REGS_EQUAL, offset);
+ }
+ BYTECODE(CHECK_NOT_BACK_REF) {
+ int32_t offset = Load32Aligned(pc + 4);
+ fprintf(stderr, " reg[%d], %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ offset);
+ BRANCH(CHECK_NOT_BACK_REF, offset);
+ }
+ BYTECODE(CHECK_NOT_BACK_REF_NO_CASE) {
+ int32_t offset = Load32Aligned(pc + 4);
+ fprintf(stderr, " reg[%d], %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ offset);
+ BRANCH(CHECK_NOT_BACK_REF_NO_CASE, offset);
+ }
+ BYTECODE(CHECK_NOT_BACK_REF_NO_CASE_UNICODE) {
+ int32_t offset = Load32Aligned(pc + 4);
+ fprintf(stderr, " reg[%d], %d",
+ insn >> irregexp::BYTECODE_SHIFT,
+ offset);
+ BRANCH(CHECK_NOT_BACK_REF_NO_CASE_UNICODE, offset);
+ }
+ BYTECODE(CHECK_AT_START) {
+ int32_t offset = Load32Aligned(pc + 4);
+ fprintf(stderr, " %d",
+ offset);
+ BRANCH(CHECK_AT_START, offset);
+ }
+ BYTECODE(CHECK_NOT_AT_START) {
+ int32_t offset = Load32Aligned(pc + 4);
+ fprintf(stderr, " %d",
+ offset);
+ BRANCH(CHECK_NOT_AT_START, offset);
+ }
+ BYTECODE(SET_CURRENT_POSITION_FROM_END) {
+ fprintf(stderr, " %u",
+ static_cast<uint32_t>(insn) >> irregexp::BYTECODE_SHIFT);
+ ADVANCE(SET_CURRENT_POSITION_FROM_END);
+ }
+ default:
+ MOZ_CRASH("Bad bytecode");
+ }
+ }
+
+#undef BYTECODE
+#undef ADVANCE
+#undef STOP
+#undef JUMP
+#undef BRANCH
+
+ return true;
+}
+
+bool
+RegExpObject::dumpBytecode(JSContext* cx, bool match_only, HandleLinearString input)
+{
+ RegExpGuard g(cx);
+ if (!getShared(cx, &g))
+ return false;
+
+ return g.re()->dumpBytecode(cx, match_only, input);
+}
+#endif
+
+template <typename CharT>
+static MOZ_ALWAYS_INLINE bool
+IsRegExpMetaChar(CharT ch)
+{
+ switch (ch) {
+ /* ES 2016 draft Mar 25, 2016 21.2.1 SyntaxCharacter. */
+ case '^': case '$': case '\\': case '.': case '*': case '+':
+ case '?': case '(': case ')': case '[': case ']': case '{':
+ case '}': case '|':
+ return true;
+ default:
+ return false;
+ }
+}
+
+template <typename CharT>
+bool
+js::HasRegExpMetaChars(const CharT* chars, size_t length)
+{
+ for (size_t i = 0; i < length; ++i) {
+ if (IsRegExpMetaChar<CharT>(chars[i]))
+ return true;
+ }
+ return false;
+}
+
+template bool
+js::HasRegExpMetaChars<Latin1Char>(const Latin1Char* chars, size_t length);
+
+template bool
+js::HasRegExpMetaChars<char16_t>(const char16_t* chars, size_t length);
+
+bool
+js::StringHasRegExpMetaChars(JSLinearString* str)
+{
+ AutoCheckCannotGC nogc;
+ if (str->hasLatin1Chars())
+ return HasRegExpMetaChars(str->latin1Chars(nogc), str->length());
+
+ return HasRegExpMetaChars(str->twoByteChars(nogc), str->length());
+}
+
+/* RegExpShared */
+
+RegExpShared::RegExpShared(JSAtom* source, RegExpFlag flags)
+ : source(source), flags(flags), parenCount(0), canStringMatch(false), marked_(false)
+{}
+
+RegExpShared::~RegExpShared()
+{
+ for (size_t i = 0; i < tables.length(); i++)
+ js_delete(tables[i]);
+}
+
+void
+RegExpShared::trace(JSTracer* trc)
+{
+ if (trc->isMarkingTracer())
+ marked_ = true;
+
+ TraceNullableEdge(trc, &source, "RegExpShared source");
+ for (auto& comp : compilationArray)
+ TraceNullableEdge(trc, &comp.jitCode, "RegExpShared code");
+}
+
+bool
+RegExpShared::isMarkedGray() const
+{
+ if (source && source->isMarked(gc::GRAY))
+ return true;
+ for (const auto& comp : compilationArray) {
+ if (comp.jitCode && comp.jitCode->isMarked(gc::GRAY))
+ return true;
+ }
+ return false;
+}
+
+void
+RegExpShared::unmarkGray()
+{
+ if (source)
+ JS::UnmarkGrayGCThingRecursively(JS::GCCellPtr(source));
+ for (const auto& comp : compilationArray) {
+ if (comp.jitCode)
+ JS::UnmarkGrayGCThingRecursively(JS::GCCellPtr(comp.jitCode.get()));
+ }
+}
+
+bool
+RegExpShared::compile(JSContext* cx, HandleLinearString input,
+ CompilationMode mode, ForceByteCodeEnum force)
+{
+ TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
+ AutoTraceLog logCompile(logger, TraceLogger_IrregexpCompile);
+
+ RootedAtom pattern(cx, source);
+ return compile(cx, pattern, input, mode, force);
+}
+
+bool
+RegExpShared::compile(JSContext* cx, HandleAtom pattern, HandleLinearString input,
+ CompilationMode mode, ForceByteCodeEnum force)
+{
+ if (!ignoreCase() && !StringHasRegExpMetaChars(pattern))
+ canStringMatch = true;
+
+ CompileOptions options(cx);
+ TokenStream dummyTokenStream(cx, options, nullptr, 0, nullptr);
+
+ LifoAllocScope scope(&cx->tempLifoAlloc());
+
+ /* Parse the pattern. */
+ irregexp::RegExpCompileData data;
+ if (!irregexp::ParsePattern(dummyTokenStream, cx->tempLifoAlloc(), pattern,
+ multiline(), mode == MatchOnly, unicode(), ignoreCase(),
+ global(), sticky(), &data))
+ {
+ return false;
+ }
+
+ this->parenCount = data.capture_count;
+
+ irregexp::RegExpCode code = irregexp::CompilePattern(cx, this, &data, input,
+ false /* global() */,
+ ignoreCase(),
+ input->hasLatin1Chars(),
+ mode == MatchOnly,
+ force == ForceByteCode,
+ sticky(), unicode());
+ if (code.empty())
+ return false;
+
+ MOZ_ASSERT(!code.jitCode || !code.byteCode);
+ MOZ_ASSERT_IF(force == ForceByteCode, code.byteCode);
+
+ RegExpCompilation& compilation = this->compilation(mode, input->hasLatin1Chars());
+ if (code.jitCode)
+ compilation.jitCode = code.jitCode;
+ else if (code.byteCode)
+ compilation.byteCode = code.byteCode;
+
+ return true;
+}
+
+bool
+RegExpShared::compileIfNecessary(JSContext* cx, HandleLinearString input,
+ CompilationMode mode, ForceByteCodeEnum force)
+{
+ if (isCompiled(mode, input->hasLatin1Chars(), force))
+ return true;
+ return compile(cx, input, mode, force);
+}
+
+RegExpRunStatus
+RegExpShared::execute(JSContext* cx, HandleLinearString input, size_t start,
+ MatchPairs* matches, size_t* endIndex)
+{
+ MOZ_ASSERT_IF(matches, !endIndex);
+ MOZ_ASSERT_IF(!matches, endIndex);
+ TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
+
+ CompilationMode mode = matches ? Normal : MatchOnly;
+
+ /* Compile the code at point-of-use. */
+ if (!compileIfNecessary(cx, input, mode, DontForceByteCode))
+ return RegExpRunStatus_Error;
+
+ /*
+ * Ensure sufficient memory for output vector.
+ * No need to initialize it. The RegExp engine fills them in on a match.
+ */
+ if (matches && !matches->allocOrExpandArray(pairCount())) {
+ ReportOutOfMemory(cx);
+ return RegExpRunStatus_Error;
+ }
+
+ size_t length = input->length();
+
+ // Reset the Irregexp backtrack stack if it grows during execution.
+ irregexp::RegExpStackScope stackScope(cx->runtime());
+
+ if (canStringMatch) {
+ MOZ_ASSERT(pairCount() == 1);
+ size_t sourceLength = source->length();
+ if (sticky()) {
+ // First part checks size_t overflow.
+ if (sourceLength + start < sourceLength || sourceLength + start > length)
+ return RegExpRunStatus_Success_NotFound;
+ if (!HasSubstringAt(input, source, start))
+ return RegExpRunStatus_Success_NotFound;
+
+ if (matches) {
+ (*matches)[0].start = start;
+ (*matches)[0].limit = start + sourceLength;
+
+ matches->checkAgainst(length);
+ } else if (endIndex) {
+ *endIndex = start + sourceLength;
+ }
+ return RegExpRunStatus_Success;
+ }
+
+ int res = StringFindPattern(input, source, start);
+ if (res == -1)
+ return RegExpRunStatus_Success_NotFound;
+
+ if (matches) {
+ (*matches)[0].start = res;
+ (*matches)[0].limit = res + sourceLength;
+
+ matches->checkAgainst(length);
+ } else if (endIndex) {
+ *endIndex = res + sourceLength;
+ }
+ return RegExpRunStatus_Success;
+ }
+
+ do {
+ jit::JitCode* code = compilation(mode, input->hasLatin1Chars()).jitCode;
+ if (!code)
+ break;
+
+ RegExpRunStatus result;
+ {
+ AutoTraceLog logJIT(logger, TraceLogger_IrregexpExecute);
+ AutoCheckCannotGC nogc;
+ if (input->hasLatin1Chars()) {
+ const Latin1Char* chars = input->latin1Chars(nogc);
+ result = irregexp::ExecuteCode(cx, code, chars, start, length, matches, endIndex);
+ } else {
+ const char16_t* chars = input->twoByteChars(nogc);
+ result = irregexp::ExecuteCode(cx, code, chars, start, length, matches, endIndex);
+ }
+ }
+
+ if (result == RegExpRunStatus_Error) {
+ // An 'Error' result is returned if a stack overflow guard or
+ // interrupt guard failed. If CheckOverRecursed doesn't throw, break
+ // out and retry the regexp in the bytecode interpreter, which can
+ // execute while tolerating future interrupts. Otherwise, if we keep
+ // getting interrupted we will never finish executing the regexp.
+ if (!jit::CheckOverRecursed(cx))
+ return RegExpRunStatus_Error;
+ break;
+ }
+
+ if (result == RegExpRunStatus_Success_NotFound)
+ return RegExpRunStatus_Success_NotFound;
+
+ MOZ_ASSERT(result == RegExpRunStatus_Success);
+
+ if (matches)
+ matches->checkAgainst(length);
+ return RegExpRunStatus_Success;
+ } while (false);
+
+ // Compile bytecode for the RegExp if necessary.
+ if (!compileIfNecessary(cx, input, mode, ForceByteCode))
+ return RegExpRunStatus_Error;
+
+ uint8_t* byteCode = compilation(mode, input->hasLatin1Chars()).byteCode;
+ AutoTraceLog logInterpreter(logger, TraceLogger_IrregexpExecute);
+
+ AutoStableStringChars inputChars(cx);
+ if (!inputChars.init(cx, input))
+ return RegExpRunStatus_Error;
+
+ RegExpRunStatus result;
+ if (inputChars.isLatin1()) {
+ const Latin1Char* chars = inputChars.latin1Range().begin().get();
+ result = irregexp::InterpretCode(cx, byteCode, chars, start, length, matches, endIndex);
+ } else {
+ const char16_t* chars = inputChars.twoByteRange().begin().get();
+ result = irregexp::InterpretCode(cx, byteCode, chars, start, length, matches, endIndex);
+ }
+
+ if (result == RegExpRunStatus_Success && matches)
+ matches->checkAgainst(length);
+ return result;
+}
+
+size_t
+RegExpShared::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
+{
+ size_t n = mallocSizeOf(this);
+
+ for (size_t i = 0; i < ArrayLength(compilationArray); i++) {
+ const RegExpCompilation& compilation = compilationArray[i];
+ if (compilation.byteCode)
+ n += mallocSizeOf(compilation.byteCode);
+ }
+
+ n += tables.sizeOfExcludingThis(mallocSizeOf);
+ for (size_t i = 0; i < tables.length(); i++)
+ n += mallocSizeOf(tables[i]);
+
+ return n;
+}
+
+/* RegExpCompartment */
+
+RegExpCompartment::RegExpCompartment(JSRuntime* rt)
+ : set_(rt),
+ matchResultTemplateObject_(nullptr),
+ optimizableRegExpPrototypeShape_(nullptr),
+ optimizableRegExpInstanceShape_(nullptr)
+{}
+
+RegExpCompartment::~RegExpCompartment()
+{
+ // Because of stray mark bits being set (see RegExpCompartment::sweep)
+ // there might still be RegExpShared instances which haven't been deleted.
+ if (set_.initialized()) {
+ for (Set::Enum e(set_); !e.empty(); e.popFront()) {
+ RegExpShared* shared = e.front();
+ js_delete(shared);
+ }
+ }
+}
+
+ArrayObject*
+RegExpCompartment::createMatchResultTemplateObject(JSContext* cx)
+{
+ MOZ_ASSERT(!matchResultTemplateObject_);
+
+ /* Create template array object */
+ RootedArrayObject templateObject(cx, NewDenseUnallocatedArray(cx, RegExpObject::MaxPairCount,
+ nullptr, TenuredObject));
+ if (!templateObject)
+ return matchResultTemplateObject_; // = nullptr
+
+ // Create a new group for the template.
+ Rooted<TaggedProto> proto(cx, templateObject->taggedProto());
+ ObjectGroup* group = ObjectGroupCompartment::makeGroup(cx, templateObject->getClass(), proto);
+ if (!group)
+ return matchResultTemplateObject_; // = nullptr
+ templateObject->setGroup(group);
+
+ /* Set dummy index property */
+ RootedValue index(cx, Int32Value(0));
+ if (!NativeDefineProperty(cx, templateObject, cx->names().index, index, nullptr, nullptr,
+ JSPROP_ENUMERATE))
+ {
+ return matchResultTemplateObject_; // = nullptr
+ }
+
+ /* Set dummy input property */
+ RootedValue inputVal(cx, StringValue(cx->runtime()->emptyString));
+ if (!NativeDefineProperty(cx, templateObject, cx->names().input, inputVal, nullptr, nullptr,
+ JSPROP_ENUMERATE))
+ {
+ return matchResultTemplateObject_; // = nullptr
+ }
+
+ // Make sure that the properties are in the right slots.
+ DebugOnly<Shape*> shape = templateObject->lastProperty();
+ MOZ_ASSERT(shape->previous()->slot() == 0 &&
+ shape->previous()->propidRef() == NameToId(cx->names().index));
+ MOZ_ASSERT(shape->slot() == 1 &&
+ shape->propidRef() == NameToId(cx->names().input));
+
+ // Make sure type information reflects the indexed properties which might
+ // be added.
+ AddTypePropertyId(cx, templateObject, JSID_VOID, TypeSet::StringType());
+ AddTypePropertyId(cx, templateObject, JSID_VOID, TypeSet::UndefinedType());
+
+ matchResultTemplateObject_.set(templateObject);
+
+ return matchResultTemplateObject_;
+}
+
+bool
+RegExpCompartment::init(JSContext* cx)
+{
+ if (!set_.init(0)) {
+ if (cx)
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+RegExpShared::needsSweep(JSRuntime* rt)
+{
+ // Sometimes RegExpShared instances are marked without the compartment
+ // being subsequently cleared. This can happen if a GC is restarted while
+ // in progress (i.e. performing a full GC in the middle of an incremental
+ // GC) or if a RegExpShared referenced via the stack is traced but is not
+ // in a zone being collected.
+ //
+ // Because of this we only treat the marked_ bit as a hint, and destroy the
+ // RegExpShared if it was accidentally marked earlier but wasn't marked by
+ // the current trace.
+ bool keep = marked() && IsMarked(rt, &source);
+ for (size_t i = 0; i < ArrayLength(compilationArray); i++) {
+ RegExpShared::RegExpCompilation& compilation = compilationArray[i];
+ if (compilation.jitCode && gc::IsAboutToBeFinalized(&compilation.jitCode))
+ keep = false;
+ }
+
+ MOZ_ASSERT(rt->isHeapMajorCollecting());
+ if (keep || rt->gc.isHeapCompacting()) {
+ clearMarked();
+ return false;
+ }
+
+ return true;
+}
+
+void
+RegExpShared::discardJitCode()
+{
+ for (size_t i = 0; i < ArrayLength(compilationArray); i++)
+ compilationArray[i].jitCode = nullptr;
+}
+
+void
+RegExpCompartment::sweep(JSRuntime* rt)
+{
+ if (!set_.initialized())
+ return;
+
+ for (Set::Enum e(set_); !e.empty(); e.popFront()) {
+ RegExpShared* shared = e.front();
+ if (shared->needsSweep(rt)) {
+ js_delete(shared);
+ e.removeFront();
+ } else {
+ // Discard code to avoid holding onto ExecutablePools.
+ if (rt->gc.isHeapCompacting())
+ shared->discardJitCode();
+ }
+ }
+
+ if (matchResultTemplateObject_ &&
+ IsAboutToBeFinalized(&matchResultTemplateObject_))
+ {
+ matchResultTemplateObject_.set(nullptr);
+ }
+
+ if (optimizableRegExpPrototypeShape_ &&
+ IsAboutToBeFinalized(&optimizableRegExpPrototypeShape_))
+ {
+ optimizableRegExpPrototypeShape_.set(nullptr);
+ }
+
+ if (optimizableRegExpInstanceShape_ &&
+ IsAboutToBeFinalized(&optimizableRegExpInstanceShape_))
+ {
+ optimizableRegExpInstanceShape_.set(nullptr);
+ }
+}
+
+bool
+RegExpCompartment::get(JSContext* cx, JSAtom* source, RegExpFlag flags, RegExpGuard* g)
+{
+ Key key(source, flags);
+ Set::AddPtr p = set_.lookupForAdd(key);
+ if (p) {
+ // Trigger a read barrier on existing RegExpShared instances fetched
+ // from the table (which only holds weak references).
+ RegExpSharedReadBarrier(cx, *p);
+
+ g->init(**p);
+ return true;
+ }
+
+ ScopedJSDeletePtr<RegExpShared> shared(cx->new_<RegExpShared>(source, flags));
+ if (!shared)
+ return false;
+
+ if (!set_.add(p, shared)) {
+ ReportOutOfMemory(cx);
+ return false;
+ }
+
+ // Trace RegExpShared instances created during an incremental GC.
+ RegExpSharedReadBarrier(cx, shared);
+
+ g->init(*shared.forget());
+ return true;
+}
+
+bool
+RegExpCompartment::get(JSContext* cx, HandleAtom atom, JSString* opt, RegExpGuard* g)
+{
+ RegExpFlag flags = RegExpFlag(0);
+ if (opt && !ParseRegExpFlags(cx, opt, &flags))
+ return false;
+
+ return get(cx, atom, flags, g);
+}
+
+size_t
+RegExpCompartment::sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf)
+{
+ size_t n = 0;
+ n += set_.sizeOfExcludingThis(mallocSizeOf);
+ for (Set::Enum e(set_); !e.empty(); e.popFront()) {
+ RegExpShared* shared = e.front();
+ n += shared->sizeOfIncludingThis(mallocSizeOf);
+ }
+ return n;
+}
+
+/* Functions */
+
+JSObject*
+js::CloneRegExpObject(JSContext* cx, JSObject* obj_)
+{
+ Rooted<RegExpObject*> regex(cx, &obj_->as<RegExpObject>());
+
+ // Unlike RegExpAlloc, all clones must use |regex|'s group. Allocate
+ // in the tenured heap to simplify embedding them in JIT code.
+ RootedObjectGroup group(cx, regex->group());
+ Rooted<RegExpObject*> clone(cx, NewObjectWithGroup<RegExpObject>(cx, group, TenuredObject));
+ if (!clone)
+ return nullptr;
+ clone->initPrivate(nullptr);
+
+ if (!EmptyShape::ensureInitialCustomShape<RegExpObject>(cx, clone))
+ return nullptr;
+
+ Rooted<JSAtom*> source(cx, regex->getSource());
+
+ RegExpGuard g(cx);
+ if (!regex->getShared(cx, &g))
+ return nullptr;
+
+ clone->initAndZeroLastIndex(source, g->getFlags(), cx);
+ clone->setShared(*g.re());
+
+ return clone;
+}
+
+static bool
+HandleRegExpFlag(RegExpFlag flag, RegExpFlag* flags)
+{
+ if (*flags & flag)
+ return false;
+ *flags = RegExpFlag(*flags | flag);
+ return true;
+}
+
+template <typename CharT>
+static size_t
+ParseRegExpFlags(const CharT* chars, size_t length, RegExpFlag* flagsOut, char16_t* lastParsedOut)
+{
+ *flagsOut = RegExpFlag(0);
+
+ for (size_t i = 0; i < length; i++) {
+ *lastParsedOut = chars[i];
+ switch (chars[i]) {
+ case 'i':
+ if (!HandleRegExpFlag(IgnoreCaseFlag, flagsOut))
+ return false;
+ break;
+ case 'g':
+ if (!HandleRegExpFlag(GlobalFlag, flagsOut))
+ return false;
+ break;
+ case 'm':
+ if (!HandleRegExpFlag(MultilineFlag, flagsOut))
+ return false;
+ break;
+ case 'y':
+ if (!HandleRegExpFlag(StickyFlag, flagsOut))
+ return false;
+ break;
+ case 'u':
+ if (!HandleRegExpFlag(UnicodeFlag, flagsOut))
+ return false;
+ break;
+ default:
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool
+js::ParseRegExpFlags(JSContext* cx, JSString* flagStr, RegExpFlag* flagsOut)
+{
+ JSLinearString* linear = flagStr->ensureLinear(cx);
+ if (!linear)
+ return false;
+
+ size_t len = linear->length();
+
+ bool ok;
+ char16_t lastParsed;
+ if (linear->hasLatin1Chars()) {
+ AutoCheckCannotGC nogc;
+ ok = ::ParseRegExpFlags(linear->latin1Chars(nogc), len, flagsOut, &lastParsed);
+ } else {
+ AutoCheckCannotGC nogc;
+ ok = ::ParseRegExpFlags(linear->twoByteChars(nogc), len, flagsOut, &lastParsed);
+ }
+
+ if (!ok) {
+ TwoByteChars range(&lastParsed, 1);
+ UniqueChars utf8(JS::CharsToNewUTF8CharsZ(nullptr, range).c_str());
+ if (!utf8)
+ return false;
+ JS_ReportErrorFlagsAndNumberUTF8(cx, JSREPORT_ERROR, GetErrorMessage, nullptr,
+ JSMSG_BAD_REGEXP_FLAG, utf8.get());
+ return false;
+ }
+
+ return true;
+}
+
+template<XDRMode mode>
+bool
+js::XDRScriptRegExpObject(XDRState<mode>* xdr, MutableHandle<RegExpObject*> objp)
+{
+ /* NB: Keep this in sync with CloneScriptRegExpObject. */
+
+ RootedAtom source(xdr->cx());
+ uint32_t flagsword = 0;
+
+ if (mode == XDR_ENCODE) {
+ MOZ_ASSERT(objp);
+ RegExpObject& reobj = *objp;
+ source = reobj.getSource();
+ flagsword = reobj.getFlags();
+ }
+ if (!XDRAtom(xdr, &source) || !xdr->codeUint32(&flagsword))
+ return false;
+ if (mode == XDR_DECODE) {
+ RegExpFlag flags = RegExpFlag(flagsword);
+ RegExpObject* reobj = RegExpObject::create(xdr->cx(), source, flags, nullptr,
+ xdr->cx()->tempLifoAlloc());
+ if (!reobj)
+ return false;
+
+ objp.set(reobj);
+ }
+ return true;
+}
+
+template bool
+js::XDRScriptRegExpObject(XDRState<XDR_ENCODE>* xdr, MutableHandle<RegExpObject*> objp);
+
+template bool
+js::XDRScriptRegExpObject(XDRState<XDR_DECODE>* xdr, MutableHandle<RegExpObject*> objp);
+
+JSObject*
+js::CloneScriptRegExpObject(JSContext* cx, RegExpObject& reobj)
+{
+ /* NB: Keep this in sync with XDRScriptRegExpObject. */
+
+ RootedAtom source(cx, reobj.getSource());
+ return RegExpObject::create(cx, source, reobj.getFlags(), nullptr, cx->tempLifoAlloc());
+}
+
+JS_FRIEND_API(bool)
+js::RegExpToSharedNonInline(JSContext* cx, HandleObject obj, js::RegExpGuard* g)
+{
+ return RegExpToShared(cx, obj, g);
+}