michael@0: /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * vim: set ts=8 sts=4 et sw=4 tw=99: michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #ifndef vm_RegExpStatics_h michael@0: #define vm_RegExpStatics_h michael@0: michael@0: #include "gc/Marking.h" michael@0: #include "vm/MatchPairs.h" michael@0: #include "vm/RegExpObject.h" michael@0: #include "vm/Runtime.h" michael@0: michael@0: namespace js { michael@0: michael@0: class GlobalObject; michael@0: michael@0: class RegExpStatics michael@0: { michael@0: /* The latest RegExp output, set after execution. */ michael@0: VectorMatchPairs matches; michael@0: HeapPtr matchesInput; michael@0: michael@0: /* michael@0: * The previous RegExp input, used to resolve lazy state. michael@0: * A raw RegExpShared cannot be stored because it may be in michael@0: * a different compartment via evalcx(). michael@0: */ michael@0: HeapPtr lazySource; michael@0: RegExpFlag lazyFlags; michael@0: size_t lazyIndex; michael@0: michael@0: /* The latest RegExp input, set before execution. */ michael@0: HeapPtr pendingInput; michael@0: RegExpFlag flags; michael@0: michael@0: /* michael@0: * If true, |matchesInput| and the |lazy*| fields may be used michael@0: * to replay the last executed RegExp, and |matches| is invalid. michael@0: */ michael@0: bool pendingLazyEvaluation; michael@0: michael@0: /* Linkage for preserving RegExpStatics during nested RegExp execution. */ michael@0: RegExpStatics *bufferLink; michael@0: bool copied; michael@0: michael@0: public: michael@0: RegExpStatics() : bufferLink(nullptr), copied(false) { clear(); } michael@0: static JSObject *create(JSContext *cx, GlobalObject *parent); michael@0: michael@0: private: michael@0: bool executeLazy(JSContext *cx); michael@0: michael@0: inline void aboutToWrite(); michael@0: inline void copyTo(RegExpStatics &dst); michael@0: michael@0: inline void restore(); michael@0: bool save(JSContext *cx, RegExpStatics *buffer) { michael@0: JS_ASSERT(!buffer->copied && !buffer->bufferLink); michael@0: buffer->bufferLink = bufferLink; michael@0: bufferLink = buffer; michael@0: if (!buffer->matches.allocOrExpandArray(matches.length())) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: inline void checkInvariants(); michael@0: michael@0: /* michael@0: * Check whether the index at |checkValidIndex| is valid (>= 0). michael@0: * If so, construct a string for it and place it in |*out|. michael@0: * If not, place undefined in |*out|. michael@0: */ michael@0: bool makeMatch(JSContext *cx, size_t checkValidIndex, size_t pairNum, MutableHandleValue out); michael@0: bool createDependent(JSContext *cx, size_t start, size_t end, MutableHandleValue out); michael@0: michael@0: void markFlagsSet(JSContext *cx); michael@0: michael@0: struct InitBuffer {}; michael@0: explicit RegExpStatics(InitBuffer) : bufferLink(nullptr), copied(false) {} michael@0: michael@0: friend class PreserveRegExpStatics; michael@0: friend class AutoRegExpStaticsBuffer; michael@0: michael@0: public: michael@0: /* Mutators. */ michael@0: inline void updateLazily(JSContext *cx, JSLinearString *input, michael@0: RegExpShared *shared, size_t lastIndex); michael@0: inline bool updateFromMatchPairs(JSContext *cx, JSLinearString *input, MatchPairs &newPairs); michael@0: michael@0: void setMultiline(JSContext *cx, bool enabled) { michael@0: aboutToWrite(); michael@0: if (enabled) { michael@0: flags = RegExpFlag(flags | MultilineFlag); michael@0: markFlagsSet(cx); michael@0: } else { michael@0: flags = RegExpFlag(flags & ~MultilineFlag); michael@0: } michael@0: } michael@0: michael@0: inline void clear(); michael@0: michael@0: /* Corresponds to JSAPI functionality to set the pending RegExp input. */ michael@0: void reset(JSContext *cx, JSString *newInput, bool newMultiline) { michael@0: aboutToWrite(); michael@0: clear(); michael@0: pendingInput = newInput; michael@0: setMultiline(cx, newMultiline); michael@0: checkInvariants(); michael@0: } michael@0: michael@0: inline void setPendingInput(JSString *newInput); michael@0: michael@0: public: michael@0: /* Default match accessor. */ michael@0: const MatchPairs &getMatches() const { michael@0: /* Safe: only used by String methods, which do not set lazy mode. */ michael@0: JS_ASSERT(!pendingLazyEvaluation); michael@0: return matches; michael@0: } michael@0: michael@0: JSString *getPendingInput() const { return pendingInput; } michael@0: michael@0: RegExpFlag getFlags() const { return flags; } michael@0: bool multiline() const { return flags & MultilineFlag; } michael@0: michael@0: /* Returns whether results for a non-empty match are present. */ michael@0: bool matched() const { michael@0: /* Safe: only used by String methods, which do not set lazy mode. */ michael@0: JS_ASSERT(!pendingLazyEvaluation); michael@0: JS_ASSERT(matches.pairCount() > 0); michael@0: return matches[0].limit - matches[0].start > 0; michael@0: } michael@0: michael@0: void mark(JSTracer *trc) { michael@0: /* michael@0: * Changes to this function must also be reflected in michael@0: * RegExpStatics::AutoRooter::trace(). michael@0: */ michael@0: if (matchesInput) michael@0: MarkString(trc, &matchesInput, "res->matchesInput"); michael@0: if (lazySource) michael@0: MarkString(trc, &lazySource, "res->lazySource"); michael@0: if (pendingInput) michael@0: MarkString(trc, &pendingInput, "res->pendingInput"); michael@0: } michael@0: michael@0: /* Value creators. */ michael@0: michael@0: bool createPendingInput(JSContext *cx, MutableHandleValue out); michael@0: bool createLastMatch(JSContext *cx, MutableHandleValue out); michael@0: bool createLastParen(JSContext *cx, MutableHandleValue out); michael@0: bool createParen(JSContext *cx, size_t pairNum, MutableHandleValue out); michael@0: bool createLeftContext(JSContext *cx, MutableHandleValue out); michael@0: bool createRightContext(JSContext *cx, MutableHandleValue out); michael@0: michael@0: /* Infallible substring creators. */ michael@0: michael@0: void getParen(size_t pairNum, JSSubString *out) const; michael@0: void getLastMatch(JSSubString *out) const; michael@0: void getLastParen(JSSubString *out) const; michael@0: void getLeftContext(JSSubString *out) const; michael@0: void getRightContext(JSSubString *out) const; michael@0: }; michael@0: michael@0: class AutoRegExpStaticsBuffer : private JS::CustomAutoRooter michael@0: { michael@0: public: michael@0: explicit AutoRegExpStaticsBuffer(JSContext *cx michael@0: MOZ_GUARD_OBJECT_NOTIFIER_PARAM) michael@0: : CustomAutoRooter(cx), statics(RegExpStatics::InitBuffer()) michael@0: { michael@0: MOZ_GUARD_OBJECT_NOTIFIER_INIT; michael@0: } michael@0: michael@0: RegExpStatics& getStatics() { return statics; } michael@0: michael@0: private: michael@0: virtual void trace(JSTracer *trc) { michael@0: if (statics.matchesInput) { michael@0: MarkStringRoot(trc, reinterpret_cast(&statics.matchesInput), michael@0: "AutoRegExpStaticsBuffer matchesInput"); michael@0: } michael@0: if (statics.lazySource) { michael@0: MarkStringRoot(trc, reinterpret_cast(&statics.lazySource), michael@0: "AutoRegExpStaticsBuffer lazySource"); michael@0: } michael@0: if (statics.pendingInput) { michael@0: MarkStringRoot(trc, reinterpret_cast(&statics.pendingInput), michael@0: "AutoRegExpStaticsBuffer pendingInput"); michael@0: } michael@0: } michael@0: michael@0: RegExpStatics statics; michael@0: MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER michael@0: }; michael@0: michael@0: class PreserveRegExpStatics michael@0: { michael@0: RegExpStatics * const original; michael@0: AutoRegExpStaticsBuffer buffer; michael@0: michael@0: public: michael@0: explicit PreserveRegExpStatics(JSContext *cx, RegExpStatics *original) michael@0: : original(original), michael@0: buffer(cx) michael@0: {} michael@0: michael@0: bool init(JSContext *cx) { michael@0: return original->save(cx, &buffer.getStatics()); michael@0: } michael@0: michael@0: ~PreserveRegExpStatics() { original->restore(); } michael@0: }; michael@0: michael@0: inline bool michael@0: RegExpStatics::createDependent(JSContext *cx, size_t start, size_t end, MutableHandleValue out) michael@0: { michael@0: /* Private function: caller must perform lazy evaluation. */ michael@0: JS_ASSERT(!pendingLazyEvaluation); michael@0: michael@0: JS_ASSERT(start <= end); michael@0: JS_ASSERT(end <= matchesInput->length()); michael@0: JSString *str = js_NewDependentString(cx, matchesInput, start, end - start); michael@0: if (!str) michael@0: return false; michael@0: out.setString(str); michael@0: return true; michael@0: } michael@0: michael@0: inline bool michael@0: RegExpStatics::createPendingInput(JSContext *cx, MutableHandleValue out) michael@0: { michael@0: /* Lazy evaluation need not be resolved to return the input. */ michael@0: out.setString(pendingInput ? pendingInput.get() : cx->runtime()->emptyString); michael@0: return true; michael@0: } michael@0: michael@0: inline bool michael@0: RegExpStatics::makeMatch(JSContext *cx, size_t checkValidIndex, size_t pairNum, michael@0: MutableHandleValue out) michael@0: { michael@0: /* Private function: caller must perform lazy evaluation. */ michael@0: JS_ASSERT(!pendingLazyEvaluation); michael@0: michael@0: bool checkWhich = checkValidIndex % 2; michael@0: size_t checkPair = checkValidIndex / 2; michael@0: michael@0: if (matches.empty() || checkPair >= matches.pairCount() || michael@0: (checkWhich ? matches[checkPair].limit : matches[checkPair].start) < 0) michael@0: { michael@0: out.setString(cx->runtime()->emptyString); michael@0: return true; michael@0: } michael@0: const MatchPair &pair = matches[pairNum]; michael@0: return createDependent(cx, pair.start, pair.limit, out); michael@0: } michael@0: michael@0: inline bool michael@0: RegExpStatics::createLastMatch(JSContext *cx, MutableHandleValue out) michael@0: { michael@0: if (!executeLazy(cx)) michael@0: return false; michael@0: return makeMatch(cx, 0, 0, out); michael@0: } michael@0: michael@0: inline bool michael@0: RegExpStatics::createLastParen(JSContext *cx, MutableHandleValue out) michael@0: { michael@0: if (!executeLazy(cx)) michael@0: return false; michael@0: michael@0: if (matches.empty() || matches.pairCount() == 1) { michael@0: out.setString(cx->runtime()->emptyString); michael@0: return true; michael@0: } michael@0: const MatchPair &pair = matches[matches.pairCount() - 1]; michael@0: if (pair.start == -1) { michael@0: out.setString(cx->runtime()->emptyString); michael@0: return true; michael@0: } michael@0: JS_ASSERT(pair.start >= 0 && pair.limit >= 0); michael@0: JS_ASSERT(pair.limit >= pair.start); michael@0: return createDependent(cx, pair.start, pair.limit, out); michael@0: } michael@0: michael@0: inline bool michael@0: RegExpStatics::createParen(JSContext *cx, size_t pairNum, MutableHandleValue out) michael@0: { michael@0: JS_ASSERT(pairNum >= 1); michael@0: if (!executeLazy(cx)) michael@0: return false; michael@0: michael@0: if (matches.empty() || pairNum >= matches.pairCount()) { michael@0: out.setString(cx->runtime()->emptyString); michael@0: return true; michael@0: } michael@0: return makeMatch(cx, pairNum * 2, pairNum, out); michael@0: } michael@0: michael@0: inline bool michael@0: RegExpStatics::createLeftContext(JSContext *cx, MutableHandleValue out) michael@0: { michael@0: if (!executeLazy(cx)) michael@0: return false; michael@0: michael@0: if (matches.empty()) { michael@0: out.setString(cx->runtime()->emptyString); michael@0: return true; michael@0: } michael@0: if (matches[0].start < 0) { michael@0: out.setUndefined(); michael@0: return true; michael@0: } michael@0: return createDependent(cx, 0, matches[0].start, out); michael@0: } michael@0: michael@0: inline bool michael@0: RegExpStatics::createRightContext(JSContext *cx, MutableHandleValue out) michael@0: { michael@0: if (!executeLazy(cx)) michael@0: return false; michael@0: michael@0: if (matches.empty()) { michael@0: out.setString(cx->runtime()->emptyString); michael@0: return true; michael@0: } michael@0: if (matches[0].limit < 0) { michael@0: out.setUndefined(); michael@0: return true; michael@0: } michael@0: return createDependent(cx, matches[0].limit, matchesInput->length(), out); michael@0: } michael@0: michael@0: inline void michael@0: RegExpStatics::getParen(size_t pairNum, JSSubString *out) const michael@0: { michael@0: JS_ASSERT(!pendingLazyEvaluation); michael@0: michael@0: JS_ASSERT(pairNum >= 1 && pairNum < matches.pairCount()); michael@0: const MatchPair &pair = matches[pairNum]; michael@0: if (pair.isUndefined()) { michael@0: *out = js_EmptySubString; michael@0: return; michael@0: } michael@0: out->chars = matchesInput->chars() + pair.start; michael@0: out->length = pair.length(); michael@0: } michael@0: michael@0: inline void michael@0: RegExpStatics::getLastMatch(JSSubString *out) const michael@0: { michael@0: JS_ASSERT(!pendingLazyEvaluation); michael@0: michael@0: if (matches.empty()) { michael@0: *out = js_EmptySubString; michael@0: return; michael@0: } michael@0: JS_ASSERT(matchesInput); michael@0: out->chars = matchesInput->chars() + matches[0].start; michael@0: JS_ASSERT(matches[0].limit >= matches[0].start); michael@0: out->length = matches[0].length(); michael@0: } michael@0: michael@0: inline void michael@0: RegExpStatics::getLastParen(JSSubString *out) const michael@0: { michael@0: JS_ASSERT(!pendingLazyEvaluation); michael@0: michael@0: /* Note: the first pair is the whole match. */ michael@0: if (matches.empty() || matches.pairCount() == 1) { michael@0: *out = js_EmptySubString; michael@0: return; michael@0: } michael@0: getParen(matches.parenCount(), out); michael@0: } michael@0: michael@0: inline void michael@0: RegExpStatics::getLeftContext(JSSubString *out) const michael@0: { michael@0: JS_ASSERT(!pendingLazyEvaluation); michael@0: michael@0: if (matches.empty()) { michael@0: *out = js_EmptySubString; michael@0: return; michael@0: } michael@0: out->chars = matchesInput->chars(); michael@0: out->length = matches[0].start; michael@0: } michael@0: michael@0: inline void michael@0: RegExpStatics::getRightContext(JSSubString *out) const michael@0: { michael@0: JS_ASSERT(!pendingLazyEvaluation); michael@0: michael@0: if (matches.empty()) { michael@0: *out = js_EmptySubString; michael@0: return; michael@0: } michael@0: out->chars = matchesInput->chars() + matches[0].limit; michael@0: JS_ASSERT(matches[0].limit <= int(matchesInput->length())); michael@0: out->length = matchesInput->length() - matches[0].limit; michael@0: } michael@0: michael@0: inline void michael@0: RegExpStatics::copyTo(RegExpStatics &dst) michael@0: { michael@0: /* Destination buffer has already been reserved by save(). */ michael@0: if (!pendingLazyEvaluation) michael@0: dst.matches.initArrayFrom(matches); michael@0: michael@0: dst.matchesInput = matchesInput; michael@0: dst.lazySource = lazySource; michael@0: dst.lazyFlags = lazyFlags; michael@0: dst.lazyIndex = lazyIndex; michael@0: dst.pendingInput = pendingInput; michael@0: dst.flags = flags; michael@0: dst.pendingLazyEvaluation = pendingLazyEvaluation; michael@0: michael@0: JS_ASSERT_IF(pendingLazyEvaluation, lazySource); michael@0: JS_ASSERT_IF(pendingLazyEvaluation, matchesInput); michael@0: } michael@0: michael@0: inline void michael@0: RegExpStatics::aboutToWrite() michael@0: { michael@0: if (bufferLink && !bufferLink->copied) { michael@0: copyTo(*bufferLink); michael@0: bufferLink->copied = true; michael@0: } michael@0: } michael@0: michael@0: inline void michael@0: RegExpStatics::restore() michael@0: { michael@0: if (bufferLink->copied) michael@0: bufferLink->copyTo(*this); michael@0: bufferLink = bufferLink->bufferLink; michael@0: } michael@0: michael@0: inline void michael@0: RegExpStatics::updateLazily(JSContext *cx, JSLinearString *input, michael@0: RegExpShared *shared, size_t lastIndex) michael@0: { michael@0: JS_ASSERT(input && shared); michael@0: aboutToWrite(); michael@0: michael@0: BarrieredSetPair(cx->zone(), michael@0: pendingInput, input, michael@0: matchesInput, input); michael@0: michael@0: lazySource = shared->source; michael@0: lazyFlags = shared->flags; michael@0: lazyIndex = lastIndex; michael@0: pendingLazyEvaluation = true; michael@0: } michael@0: michael@0: inline bool michael@0: RegExpStatics::updateFromMatchPairs(JSContext *cx, JSLinearString *input, MatchPairs &newPairs) michael@0: { michael@0: JS_ASSERT(input); michael@0: aboutToWrite(); michael@0: michael@0: /* Unset all lazy state. */ michael@0: pendingLazyEvaluation = false; michael@0: this->lazySource = nullptr; michael@0: this->lazyIndex = size_t(-1); michael@0: michael@0: BarrieredSetPair(cx->zone(), michael@0: pendingInput, input, michael@0: matchesInput, input); michael@0: michael@0: if (!matches.initArrayFrom(newPairs)) { michael@0: js_ReportOutOfMemory(cx); michael@0: return false; michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: inline void michael@0: RegExpStatics::clear() michael@0: { michael@0: aboutToWrite(); michael@0: michael@0: matches.forgetArray(); michael@0: matchesInput = nullptr; michael@0: lazySource = nullptr; michael@0: lazyFlags = RegExpFlag(0); michael@0: lazyIndex = size_t(-1); michael@0: pendingInput = nullptr; michael@0: flags = RegExpFlag(0); michael@0: pendingLazyEvaluation = false; michael@0: } michael@0: michael@0: inline void michael@0: RegExpStatics::setPendingInput(JSString *newInput) michael@0: { michael@0: aboutToWrite(); michael@0: pendingInput = newInput; michael@0: } michael@0: michael@0: inline void michael@0: RegExpStatics::checkInvariants() michael@0: { michael@0: #ifdef DEBUG michael@0: if (pendingLazyEvaluation) { michael@0: JS_ASSERT(lazySource); michael@0: JS_ASSERT(matchesInput); michael@0: JS_ASSERT(lazyIndex != size_t(-1)); michael@0: return; michael@0: } michael@0: michael@0: if (matches.empty()) { michael@0: JS_ASSERT(!matchesInput); michael@0: return; michael@0: } michael@0: michael@0: /* Pair count is non-zero, so there must be match pairs input. */ michael@0: JS_ASSERT(matchesInput); michael@0: size_t mpiLen = matchesInput->length(); michael@0: michael@0: /* Both members of the first pair must be non-negative. */ michael@0: JS_ASSERT(!matches[0].isUndefined()); michael@0: JS_ASSERT(matches[0].limit >= 0); michael@0: michael@0: /* Present pairs must be valid. */ michael@0: for (size_t i = 0; i < matches.pairCount(); i++) { michael@0: if (matches[i].isUndefined()) michael@0: continue; michael@0: const MatchPair &pair = matches[i]; michael@0: JS_ASSERT(mpiLen >= size_t(pair.limit) && pair.limit >= pair.start && pair.start >= 0); michael@0: } michael@0: #endif /* DEBUG */ michael@0: } michael@0: michael@0: } /* namespace js */ michael@0: michael@0: #endif /* vm_RegExpStatics_h */