416 lines
11 KiB
C++
416 lines
11 KiB
C++
/* -*- 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/. */
|
|
|
|
#ifndef vm_RegExpStatics_h
|
|
#define vm_RegExpStatics_h
|
|
|
|
#include "gc/Marking.h"
|
|
#include "vm/MatchPairs.h"
|
|
#include "vm/RegExpObject.h"
|
|
#include "vm/Runtime.h"
|
|
|
|
namespace js {
|
|
|
|
class GlobalObject;
|
|
class RegExpStaticsObject;
|
|
|
|
class RegExpStatics
|
|
{
|
|
/* The latest RegExp output, set after execution. */
|
|
VectorMatchPairs matches;
|
|
HeapPtr<JSLinearString*> matchesInput;
|
|
|
|
/*
|
|
* The previous RegExp input, used to resolve lazy state.
|
|
* A raw RegExpShared cannot be stored because it may be in
|
|
* a different compartment via evalcx().
|
|
*/
|
|
HeapPtr<JSAtom*> lazySource;
|
|
RegExpFlag lazyFlags;
|
|
size_t lazyIndex;
|
|
|
|
/* The latest RegExp input, set before execution. */
|
|
HeapPtr<JSString*> pendingInput;
|
|
|
|
/*
|
|
* If non-zero, |matchesInput| and the |lazy*| fields may be used
|
|
* to replay the last executed RegExp, and |matches| is invalid.
|
|
*/
|
|
int32_t pendingLazyEvaluation;
|
|
|
|
public:
|
|
RegExpStatics() { clear(); }
|
|
static RegExpStaticsObject* create(ExclusiveContext* cx, Handle<GlobalObject*> parent);
|
|
|
|
private:
|
|
bool executeLazy(JSContext* cx);
|
|
|
|
inline void checkInvariants();
|
|
|
|
/*
|
|
* Check whether a match for pair |pairNum| occurred. If so, allocate and
|
|
* store the match string in |*out|; otherwise place |undefined| in |*out|.
|
|
*/
|
|
bool makeMatch(JSContext* cx, size_t pairNum, MutableHandleValue out);
|
|
bool createDependent(JSContext* cx, size_t start, size_t end, MutableHandleValue out);
|
|
|
|
struct InitBuffer {};
|
|
explicit RegExpStatics(InitBuffer) {}
|
|
|
|
public:
|
|
/* Mutators. */
|
|
inline void updateLazily(JSContext* cx, JSLinearString* input,
|
|
RegExpShared* shared, size_t lastIndex);
|
|
inline bool updateFromMatchPairs(JSContext* cx, JSLinearString* input, MatchPairs& newPairs);
|
|
|
|
inline void clear();
|
|
|
|
/* Corresponds to JSAPI functionality to set the pending RegExp input. */
|
|
void reset(JSContext* cx, JSString* newInput) {
|
|
clear();
|
|
pendingInput = newInput;
|
|
checkInvariants();
|
|
}
|
|
|
|
inline void setPendingInput(JSString* newInput);
|
|
|
|
public:
|
|
/* Default match accessor. */
|
|
const MatchPairs& getMatches() const {
|
|
/* Safe: only used by String methods, which do not set lazy mode. */
|
|
MOZ_ASSERT(!pendingLazyEvaluation);
|
|
return matches;
|
|
}
|
|
|
|
JSString* getPendingInput() const { return pendingInput; }
|
|
|
|
void mark(JSTracer* trc) {
|
|
/*
|
|
* Changes to this function must also be reflected in
|
|
* RegExpStatics::AutoRooter::trace().
|
|
*/
|
|
TraceNullableEdge(trc, &matchesInput, "res->matchesInput");
|
|
TraceNullableEdge(trc, &lazySource, "res->lazySource");
|
|
TraceNullableEdge(trc, &pendingInput, "res->pendingInput");
|
|
}
|
|
|
|
/* Value creators. */
|
|
|
|
bool createPendingInput(JSContext* cx, MutableHandleValue out);
|
|
bool createLastMatch(JSContext* cx, MutableHandleValue out);
|
|
bool createLastParen(JSContext* cx, MutableHandleValue out);
|
|
bool createParen(JSContext* cx, size_t pairNum, MutableHandleValue out);
|
|
bool createLeftContext(JSContext* cx, MutableHandleValue out);
|
|
bool createRightContext(JSContext* cx, MutableHandleValue out);
|
|
|
|
/* Infallible substring creators. */
|
|
|
|
void getParen(size_t pairNum, JSSubString* out) const;
|
|
void getLastMatch(JSSubString* out) const;
|
|
void getLastParen(JSSubString* out) const;
|
|
void getLeftContext(JSSubString* out) const;
|
|
void getRightContext(JSSubString* out) const;
|
|
|
|
static size_t offsetOfPendingInput() {
|
|
return offsetof(RegExpStatics, pendingInput);
|
|
}
|
|
|
|
static size_t offsetOfMatchesInput() {
|
|
return offsetof(RegExpStatics, matchesInput);
|
|
}
|
|
|
|
static size_t offsetOfLazySource() {
|
|
return offsetof(RegExpStatics, lazySource);
|
|
}
|
|
|
|
static size_t offsetOfLazyFlags() {
|
|
return offsetof(RegExpStatics, lazyFlags);
|
|
}
|
|
|
|
static size_t offsetOfLazyIndex() {
|
|
return offsetof(RegExpStatics, lazyIndex);
|
|
}
|
|
|
|
static size_t offsetOfPendingLazyEvaluation() {
|
|
return offsetof(RegExpStatics, pendingLazyEvaluation);
|
|
}
|
|
};
|
|
|
|
inline bool
|
|
RegExpStatics::createDependent(JSContext* cx, size_t start, size_t end, MutableHandleValue out)
|
|
{
|
|
/* Private function: caller must perform lazy evaluation. */
|
|
MOZ_ASSERT(!pendingLazyEvaluation);
|
|
|
|
MOZ_ASSERT(start <= end);
|
|
MOZ_ASSERT(end <= matchesInput->length());
|
|
JSString* str = NewDependentString(cx, matchesInput, start, end - start);
|
|
if (!str)
|
|
return false;
|
|
out.setString(str);
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
RegExpStatics::createPendingInput(JSContext* cx, MutableHandleValue out)
|
|
{
|
|
/* Lazy evaluation need not be resolved to return the input. */
|
|
out.setString(pendingInput ? pendingInput.get() : cx->runtime()->emptyString);
|
|
return true;
|
|
}
|
|
|
|
inline bool
|
|
RegExpStatics::makeMatch(JSContext* cx, size_t pairNum, MutableHandleValue out)
|
|
{
|
|
/* Private function: caller must perform lazy evaluation. */
|
|
MOZ_ASSERT(!pendingLazyEvaluation);
|
|
|
|
if (matches.empty() || pairNum >= matches.pairCount() || matches[pairNum].isUndefined()) {
|
|
out.setUndefined();
|
|
return true;
|
|
}
|
|
|
|
const MatchPair& pair = matches[pairNum];
|
|
return createDependent(cx, pair.start, pair.limit, out);
|
|
}
|
|
|
|
inline bool
|
|
RegExpStatics::createLastMatch(JSContext* cx, MutableHandleValue out)
|
|
{
|
|
if (!executeLazy(cx))
|
|
return false;
|
|
return makeMatch(cx, 0, out);
|
|
}
|
|
|
|
inline bool
|
|
RegExpStatics::createLastParen(JSContext* cx, MutableHandleValue out)
|
|
{
|
|
if (!executeLazy(cx))
|
|
return false;
|
|
|
|
if (matches.empty() || matches.pairCount() == 1) {
|
|
out.setString(cx->runtime()->emptyString);
|
|
return true;
|
|
}
|
|
const MatchPair& pair = matches[matches.pairCount() - 1];
|
|
if (pair.start == -1) {
|
|
out.setString(cx->runtime()->emptyString);
|
|
return true;
|
|
}
|
|
MOZ_ASSERT(pair.start >= 0 && pair.limit >= 0);
|
|
MOZ_ASSERT(pair.limit >= pair.start);
|
|
return createDependent(cx, pair.start, pair.limit, out);
|
|
}
|
|
|
|
inline bool
|
|
RegExpStatics::createParen(JSContext* cx, size_t pairNum, MutableHandleValue out)
|
|
{
|
|
MOZ_ASSERT(pairNum >= 1);
|
|
if (!executeLazy(cx))
|
|
return false;
|
|
|
|
if (matches.empty() || pairNum >= matches.pairCount()) {
|
|
out.setString(cx->runtime()->emptyString);
|
|
return true;
|
|
}
|
|
return makeMatch(cx, pairNum, out);
|
|
}
|
|
|
|
inline bool
|
|
RegExpStatics::createLeftContext(JSContext* cx, MutableHandleValue out)
|
|
{
|
|
if (!executeLazy(cx))
|
|
return false;
|
|
|
|
if (matches.empty()) {
|
|
out.setString(cx->runtime()->emptyString);
|
|
return true;
|
|
}
|
|
if (matches[0].start < 0) {
|
|
out.setUndefined();
|
|
return true;
|
|
}
|
|
return createDependent(cx, 0, matches[0].start, out);
|
|
}
|
|
|
|
inline bool
|
|
RegExpStatics::createRightContext(JSContext* cx, MutableHandleValue out)
|
|
{
|
|
if (!executeLazy(cx))
|
|
return false;
|
|
|
|
if (matches.empty()) {
|
|
out.setString(cx->runtime()->emptyString);
|
|
return true;
|
|
}
|
|
if (matches[0].limit < 0) {
|
|
out.setUndefined();
|
|
return true;
|
|
}
|
|
return createDependent(cx, matches[0].limit, matchesInput->length(), out);
|
|
}
|
|
|
|
inline void
|
|
RegExpStatics::getParen(size_t pairNum, JSSubString* out) const
|
|
{
|
|
MOZ_ASSERT(!pendingLazyEvaluation);
|
|
|
|
MOZ_ASSERT(pairNum >= 1 && pairNum < matches.pairCount());
|
|
const MatchPair& pair = matches[pairNum];
|
|
if (pair.isUndefined()) {
|
|
out->initEmpty(matchesInput);
|
|
return;
|
|
}
|
|
out->init(matchesInput, pair.start, pair.length());
|
|
}
|
|
|
|
inline void
|
|
RegExpStatics::getLastMatch(JSSubString* out) const
|
|
{
|
|
MOZ_ASSERT(!pendingLazyEvaluation);
|
|
|
|
if (matches.empty()) {
|
|
out->initEmpty(matchesInput);
|
|
return;
|
|
}
|
|
MOZ_ASSERT(matchesInput);
|
|
MOZ_ASSERT(matches[0].limit >= matches[0].start);
|
|
out->init(matchesInput, matches[0].start, matches[0].length());
|
|
}
|
|
|
|
inline void
|
|
RegExpStatics::getLastParen(JSSubString* out) const
|
|
{
|
|
MOZ_ASSERT(!pendingLazyEvaluation);
|
|
|
|
/* Note: the first pair is the whole match. */
|
|
if (matches.empty() || matches.pairCount() == 1) {
|
|
out->initEmpty(matchesInput);
|
|
return;
|
|
}
|
|
getParen(matches.parenCount(), out);
|
|
}
|
|
|
|
inline void
|
|
RegExpStatics::getLeftContext(JSSubString* out) const
|
|
{
|
|
MOZ_ASSERT(!pendingLazyEvaluation);
|
|
|
|
if (matches.empty()) {
|
|
out->initEmpty(matchesInput);
|
|
return;
|
|
}
|
|
out->init(matchesInput, 0, matches[0].start);
|
|
}
|
|
|
|
inline void
|
|
RegExpStatics::getRightContext(JSSubString* out) const
|
|
{
|
|
MOZ_ASSERT(!pendingLazyEvaluation);
|
|
|
|
if (matches.empty()) {
|
|
out->initEmpty(matchesInput);
|
|
return;
|
|
}
|
|
MOZ_ASSERT(matches[0].limit <= int(matchesInput->length()));
|
|
size_t length = matchesInput->length() - matches[0].limit;
|
|
out->init(matchesInput, matches[0].limit, length);
|
|
}
|
|
|
|
inline void
|
|
RegExpStatics::updateLazily(JSContext* cx, JSLinearString* input,
|
|
RegExpShared* shared, size_t lastIndex)
|
|
{
|
|
MOZ_ASSERT(input && shared);
|
|
|
|
BarrieredSetPair<JSString, JSLinearString>(cx->zone(),
|
|
pendingInput, input,
|
|
matchesInput, input);
|
|
|
|
lazySource = shared->source;
|
|
lazyFlags = shared->flags;
|
|
lazyIndex = lastIndex;
|
|
pendingLazyEvaluation = 1;
|
|
}
|
|
|
|
inline bool
|
|
RegExpStatics::updateFromMatchPairs(JSContext* cx, JSLinearString* input, MatchPairs& newPairs)
|
|
{
|
|
MOZ_ASSERT(input);
|
|
|
|
/* Unset all lazy state. */
|
|
pendingLazyEvaluation = false;
|
|
this->lazySource = nullptr;
|
|
this->lazyIndex = size_t(-1);
|
|
|
|
BarrieredSetPair<JSString, JSLinearString>(cx->zone(),
|
|
pendingInput, input,
|
|
matchesInput, input);
|
|
|
|
if (!matches.initArrayFrom(newPairs)) {
|
|
ReportOutOfMemory(cx);
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
inline void
|
|
RegExpStatics::clear()
|
|
{
|
|
matches.forgetArray();
|
|
matchesInput = nullptr;
|
|
lazySource = nullptr;
|
|
lazyFlags = RegExpFlag(0);
|
|
lazyIndex = size_t(-1);
|
|
pendingInput = nullptr;
|
|
pendingLazyEvaluation = false;
|
|
}
|
|
|
|
inline void
|
|
RegExpStatics::setPendingInput(JSString* newInput)
|
|
{
|
|
pendingInput = newInput;
|
|
}
|
|
|
|
inline void
|
|
RegExpStatics::checkInvariants()
|
|
{
|
|
#ifdef DEBUG
|
|
if (pendingLazyEvaluation) {
|
|
MOZ_ASSERT(lazySource);
|
|
MOZ_ASSERT(matchesInput);
|
|
MOZ_ASSERT(lazyIndex != size_t(-1));
|
|
return;
|
|
}
|
|
|
|
if (matches.empty()) {
|
|
MOZ_ASSERT(!matchesInput);
|
|
return;
|
|
}
|
|
|
|
/* Pair count is non-zero, so there must be match pairs input. */
|
|
MOZ_ASSERT(matchesInput);
|
|
size_t mpiLen = matchesInput->length();
|
|
|
|
/* Both members of the first pair must be non-negative. */
|
|
MOZ_ASSERT(!matches[0].isUndefined());
|
|
MOZ_ASSERT(matches[0].limit >= 0);
|
|
|
|
/* Present pairs must be valid. */
|
|
for (size_t i = 0; i < matches.pairCount(); i++) {
|
|
if (matches[i].isUndefined())
|
|
continue;
|
|
const MatchPair& pair = matches[i];
|
|
MOZ_ASSERT(mpiLen >= size_t(pair.limit) && pair.limit >= pair.start && pair.start >= 0);
|
|
}
|
|
#endif /* DEBUG */
|
|
}
|
|
|
|
} /* namespace js */
|
|
|
|
#endif /* vm_RegExpStatics_h */
|