blob: d91ac9d4674cd8ebac50f478f870ea87cf51f464 [file] [log] [blame]
/* -*- 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 "frontend/TokenStream.h"
#include "vm/MatchPairs.h"
#include "vm/StringBuffer.h"
#include "jsobjinlines.h"
#include "vm/RegExpObject-inl.h"
#include "vm/RegExpStatics-inl.h"
#include "vm/Xdr.h"
#include "nb/memory_scope.h"
using namespace js;
using js::frontend::TokenStream;
JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD);
JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB);
JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE);
JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY);
/* RegExpObjectBuilder */
RegExpObjectBuilder::RegExpObjectBuilder(JSContext *cx, RegExpObject *reobj)
: cx(cx), reobj_(cx, reobj)
{}
bool
RegExpObjectBuilder::getOrCreate()
{
TRACK_MEMORY_SCOPE("Javascript");
if (reobj_)
return true;
// Note: RegExp objects are always allocated in the tenured heap. This is
// not strictly required, but simplifies embedding them in jitcode.
JSObject *obj = NewBuiltinClassInstance(cx, &RegExpObject::class_, TenuredObject);
if (!obj)
return false;
obj->initPrivate(NULL);
reobj_ = &obj->as<RegExpObject>();
return true;
}
bool
RegExpObjectBuilder::getOrCreateClone(RegExpObject *proto)
{
TRACK_MEMORY_SCOPE("Javascript");
JS_ASSERT(!reobj_);
// Note: RegExp objects are always allocated in the tenured heap. This is
// not strictly required, but simplifies embedding them in jitcode.
JSObject *clone = NewObjectWithGivenProto(cx, &RegExpObject::class_, proto, proto->getParent(),
TenuredObject);
if (!clone)
return false;
clone->initPrivate(NULL);
reobj_ = &clone->as<RegExpObject>();
return true;
}
RegExpObject *
RegExpObjectBuilder::build(HandleAtom source, RegExpShared &shared)
{
if (!getOrCreate())
return NULL;
if (!reobj_->init(cx, source, shared.getFlags()))
return NULL;
reobj_->setShared(cx, shared);
return reobj_;
}
RegExpObject *
RegExpObjectBuilder::build(HandleAtom source, RegExpFlag flags)
{
if (!getOrCreate())
return NULL;
return reobj_->init(cx, source, flags) ? reobj_.get() : NULL;
}
RegExpObject *
RegExpObjectBuilder::clone(Handle<RegExpObject *> other, Handle<RegExpObject *> proto)
{
TRACK_MEMORY_SCOPE("Javascript");
if (!getOrCreateClone(proto))
return NULL;
/*
* Check that the RegExpShared for the original is okay to use in
* the clone -- if the |RegExpStatics| provides more flags we'll
* need a different |RegExpShared|.
*/
RegExpStatics *res = proto->getParent()->as<GlobalObject>().getRegExpStatics();
RegExpFlag origFlags = other->getFlags();
RegExpFlag staticsFlags = res->getFlags();
if ((origFlags & staticsFlags) != staticsFlags) {
RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags);
Rooted<JSAtom *> source(cx, other->getSource());
return build(source, newFlags);
}
RegExpGuard g(cx);
if (!other->getShared(cx, &g))
return NULL;
Rooted<JSAtom *> source(cx, other->getSource());
return build(source, *g);
}
/* MatchPairs */
bool
MatchPairs::initArray(size_t pairCount)
{
TRACK_MEMORY_SCOPE("Javascript");
JS_ASSERT(pairCount > 0);
/* Guarantee adequate space in buffer. */
if (!allocOrExpandArray(pairCount))
return false;
/* Initialize all MatchPair objects to invalid locations. */
for (size_t i = 0; i < pairCount; i++) {
pairs_[i].start = -1;
pairs_[i].limit = -1;
}
return true;
}
bool
MatchPairs::initArrayFrom(MatchPairs &copyFrom)
{
TRACK_MEMORY_SCOPE("Javascript");
JS_ASSERT(copyFrom.pairCount() > 0);
if (!allocOrExpandArray(copyFrom.pairCount()))
return false;
for (size_t i = 0; i < pairCount_; i++) {
JS_ASSERT(copyFrom[i].check());
pairs_[i].start = copyFrom[i].start;
pairs_[i].limit = copyFrom[i].limit;
}
return true;
}
void
MatchPairs::displace(size_t disp)
{
if (disp == 0)
return;
for (size_t i = 0; i < pairCount_; i++) {
JS_ASSERT(pairs_[i].check());
pairs_[i].start += (pairs_[i].start < 0) ? 0 : disp;
pairs_[i].limit += (pairs_[i].limit < 0) ? 0 : disp;
}
}
bool
ScopedMatchPairs::allocOrExpandArray(size_t pairCount)
{
TRACK_MEMORY_SCOPE("Javascript");
/* Array expansion is forbidden, but array reuse is acceptable. */
if (pairCount_) {
JS_ASSERT(pairs_);
JS_ASSERT(pairCount_ == pairCount);
return true;
}
JS_ASSERT(!pairs_);
pairs_ = (MatchPair *)lifoScope_.alloc().alloc(sizeof(MatchPair) * pairCount);
if (!pairs_)
return false;
pairCount_ = pairCount;
return true;
}
bool
VectorMatchPairs::allocOrExpandArray(size_t pairCount)
{
TRACK_MEMORY_SCOPE("Javascript");
if (!vec_.resizeUninitialized(sizeof(MatchPair) * pairCount))
return false;
pairs_ = &vec_[0];
pairCount_ = pairCount;
return true;
}
/* RegExpObject */
static void
regexp_trace(JSTracer *trc, JSObject *obj)
{
/*
* We have to check both conditions, since:
* 1. During TraceRuntime, isHeapBusy() is true
* 2. When a write barrier executes, IS_GC_MARKING_TRACER is true.
*/
if (trc->runtime->isHeapBusy() && IS_GC_MARKING_TRACER(trc))
obj->setPrivate(NULL);
}
Class RegExpObject::class_ = {
js_RegExp_str,
JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS |
JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) |
JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp),
JS_PropertyStub, /* addProperty */
JS_DeletePropertyStub, /* delProperty */
JS_PropertyStub, /* getProperty */
JS_StrictPropertyStub, /* setProperty */
JS_EnumerateStub, /* enumerate */
JS_ResolveStub,
JS_ConvertStub,
NULL, /* finalize */
NULL, /* checkAccess */
NULL, /* call */
NULL, /* construct */
NULL, /* hasInstance */
regexp_trace
};
RegExpObject *
RegExpObject::create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length,
RegExpFlag flags, TokenStream *tokenStream)
{
TRACK_MEMORY_SCOPE("Javascript");
RegExpFlag staticsFlags = res->getFlags();
return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream);
}
RegExpObject *
RegExpObject::createNoStatics(JSContext *cx, const jschar *chars, size_t length, RegExpFlag flags,
TokenStream *tokenStream)
{
RootedAtom source(cx, AtomizeChars<CanGC>(cx, chars, length));
if (!source)
return NULL;
return createNoStatics(cx, source, flags, tokenStream);
}
RegExpObject *
RegExpObject::createNoStatics(JSContext *cx, HandleAtom source, RegExpFlag flags,
TokenStream *tokenStream)
{
if (!RegExpShared::checkSyntax(cx, tokenStream, source))
return NULL;
RegExpObjectBuilder builder(cx);
return builder.build(source, flags);
}
bool
RegExpObject::createShared(JSContext *cx, RegExpGuard *g)
{
TRACK_MEMORY_SCOPE("Javascript");
Rooted<RegExpObject*> self(cx, this);
JS_ASSERT(!maybeShared());
if (!cx->compartment()->regExps.get(cx, getSource(), getFlags(), g))
return false;
self->setShared(cx, **g);
return true;
}
Shape *
RegExpObject::assignInitialShape(JSContext *cx)
{
TRACK_MEMORY_SCOPE("Javascript");
JS_ASSERT(is<RegExpObject>());
JS_ASSERT(nativeEmpty());
JS_STATIC_ASSERT(LAST_INDEX_SLOT == 0);
JS_STATIC_ASSERT(SOURCE_SLOT == LAST_INDEX_SLOT + 1);
JS_STATIC_ASSERT(GLOBAL_FLAG_SLOT == SOURCE_SLOT + 1);
JS_STATIC_ASSERT(IGNORE_CASE_FLAG_SLOT == GLOBAL_FLAG_SLOT + 1);
JS_STATIC_ASSERT(MULTILINE_FLAG_SLOT == IGNORE_CASE_FLAG_SLOT + 1);
JS_STATIC_ASSERT(STICKY_FLAG_SLOT == MULTILINE_FLAG_SLOT + 1);
RootedObject self(cx, this);
/* The lastIndex property alone is writable but non-configurable. */
if (!addDataProperty(cx, cx->names().lastIndex, LAST_INDEX_SLOT, JSPROP_PERMANENT))
return NULL;
/* Remaining instance properties are non-writable and non-configurable. */
unsigned attrs = JSPROP_PERMANENT | JSPROP_READONLY;
if (!self->addDataProperty(cx, cx->names().source, SOURCE_SLOT, attrs))
return NULL;
if (!self->addDataProperty(cx, cx->names().global, GLOBAL_FLAG_SLOT, attrs))
return NULL;
if (!self->addDataProperty(cx, cx->names().ignoreCase, IGNORE_CASE_FLAG_SLOT, attrs))
return NULL;
if (!self->addDataProperty(cx, cx->names().multiline, MULTILINE_FLAG_SLOT, attrs))
return NULL;
return self->addDataProperty(cx, cx->names().sticky, STICKY_FLAG_SLOT, attrs);
}
bool
RegExpObject::init(JSContext *cx, HandleAtom source, RegExpFlag flags)
{
TRACK_MEMORY_SCOPE("Javascript");
Rooted<RegExpObject *> self(cx, this);
if (nativeEmpty()) {
if (isDelegate()) {
if (!assignInitialShape(cx))
return false;
} else {
RootedShape shape(cx, assignInitialShape(cx));
if (!shape)
return false;
RootedObject proto(cx, self->getProto());
EmptyShape::insertInitialShape(cx, shape, proto);
}
JS_ASSERT(!self->nativeEmpty());
}
JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().lastIndex))->slot() ==
LAST_INDEX_SLOT);
JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().source))->slot() ==
SOURCE_SLOT);
JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().global))->slot() ==
GLOBAL_FLAG_SLOT);
JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().ignoreCase))->slot() ==
IGNORE_CASE_FLAG_SLOT);
JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().multiline))->slot() ==
MULTILINE_FLAG_SLOT);
JS_ASSERT(self->nativeLookup(cx, NameToId(cx->names().sticky))->slot() ==
STICKY_FLAG_SLOT);
/*
* If this is a re-initialization with an existing RegExpShared, 'flags'
* may not match getShared()->flags, so forget the RegExpShared.
*/
self->JSObject::setPrivate(NULL);
self->zeroLastIndex();
self->setSource(source);
self->setGlobal(flags & GlobalFlag);
self->setIgnoreCase(flags & IgnoreCaseFlag);
self->setMultiline(flags & MultilineFlag);
self->setSticky(flags & StickyFlag);
return true;
}
JSFlatString *
RegExpObject::toString(JSContext *cx) const
{
TRACK_MEMORY_SCOPE("Javascript");
JSAtom *src = getSource();
StringBuffer sb(cx);
if (size_t len = src->length()) {
if (!sb.reserve(len + 2))
return NULL;
sb.infallibleAppend('/');
sb.infallibleAppend(src->chars(), len);
sb.infallibleAppend('/');
} else {
if (!sb.append("/(?:)/"))
return NULL;
}
if (global() && !sb.append('g'))
return NULL;
if (ignoreCase() && !sb.append('i'))
return NULL;
if (multiline() && !sb.append('m'))
return NULL;
if (sticky() && !sb.append('y'))
return NULL;
return sb.finishString();
}
/* RegExpShared */
RegExpShared::RegExpShared(JSRuntime *rt, JSAtom *source, RegExpFlag flags)
: source(source), flags(flags), parenCount(0),
#if ENABLE_YARR_JIT
codeBlock(),
#endif
bytecode(NULL), activeUseCount(0), gcNumberWhenUsed(rt->gcNumber)
{}
RegExpShared::~RegExpShared()
{
#if ENABLE_YARR_JIT
codeBlock.release();
#endif
if (bytecode)
js_delete<BytecodePattern>(bytecode);
}
void
RegExpShared::reportYarrError(JSContext *cx, TokenStream *ts, ErrorCode error)
{
switch (error) {
case JSC::Yarr::NoError:
JS_NOT_REACHED("Called reportYarrError with value for no error");
return;
#define COMPILE_EMSG(__code, __msg) \
case JSC::Yarr::__code: \
if (ts) \
ts->reportError(__msg); \
else \
JS_ReportErrorFlagsAndNumberUC(cx, JSREPORT_ERROR, js_GetErrorMessage, NULL, __msg); \
return
COMPILE_EMSG(PatternTooLarge, JSMSG_REGEXP_TOO_COMPLEX);
COMPILE_EMSG(QuantifierOutOfOrder, JSMSG_BAD_QUANTIFIER);
COMPILE_EMSG(QuantifierWithoutAtom, JSMSG_BAD_QUANTIFIER);
COMPILE_EMSG(MissingParentheses, JSMSG_MISSING_PAREN);
COMPILE_EMSG(ParenthesesUnmatched, JSMSG_UNMATCHED_RIGHT_PAREN);
COMPILE_EMSG(ParenthesesTypeInvalid, JSMSG_BAD_QUANTIFIER); /* "(?" with bad next char */
COMPILE_EMSG(CharacterClassUnmatched, JSMSG_BAD_CLASS_RANGE);
COMPILE_EMSG(CharacterClassInvalidRange, JSMSG_BAD_CLASS_RANGE);
COMPILE_EMSG(CharacterClassOutOfOrder, JSMSG_BAD_CLASS_RANGE);
COMPILE_EMSG(QuantifierTooLarge, JSMSG_BAD_QUANTIFIER);
COMPILE_EMSG(EscapeUnterminated, JSMSG_TRAILING_SLASH);
#undef COMPILE_EMSG
default:
JS_NOT_REACHED("Unknown Yarr error code");
}
}
bool
RegExpShared::checkSyntax(JSContext *cx, TokenStream *tokenStream, JSLinearString *source)
{
ErrorCode error = JSC::Yarr::checkSyntax(*source);
if (error == JSC::Yarr::NoError)
return true;
reportYarrError(cx, tokenStream, error);
return false;
}
bool
RegExpShared::compile(JSContext *cx, bool matchOnly)
{
TRACK_MEMORY_SCOPE("Javascript");
if (!sticky())
return compile(cx, *source, matchOnly);
/*
* The sticky case we implement hackily by prepending a caret onto the front
* and relying on |::execute| to pseudo-slice the string when it sees a sticky regexp.
*/
static const jschar prefix[] = {'^', '(', '?', ':'};
static const jschar postfix[] = {')'};
using mozilla::ArrayLength;
StringBuffer sb(cx);
if (!sb.reserve(ArrayLength(prefix) + source->length() + ArrayLength(postfix)))
return false;
sb.infallibleAppend(prefix, ArrayLength(prefix));
sb.infallibleAppend(source->chars(), source->length());
sb.infallibleAppend(postfix, ArrayLength(postfix));
JSAtom *fakeySource = sb.finishAtom();
if (!fakeySource)
return false;
return compile(cx, *fakeySource, matchOnly);
}
bool
RegExpShared::compile(JSContext *cx, JSLinearString &pattern, bool matchOnly)
{
TRACK_MEMORY_SCOPE("Javascript");
/* Parse the pattern. */
ErrorCode yarrError;
YarrPattern yarrPattern(pattern, ignoreCase(), multiline(), &yarrError);
if (yarrError) {
reportYarrError(cx, NULL, yarrError);
return false;
}
this->parenCount = yarrPattern.m_numSubpatterns;
#if ENABLE_YARR_JIT
if (isJITRuntimeEnabled(cx) && !yarrPattern.m_containsBackreferences) {
JSC::ExecutableAllocator *execAlloc = cx->runtime()->getExecAlloc(cx);
if (!execAlloc)
return false;
JSGlobalData globalData(execAlloc);
YarrJITCompileMode compileMode = matchOnly ? JSC::Yarr::MatchOnly
: JSC::Yarr::IncludeSubpatterns;
jitCompile(yarrPattern, JSC::Yarr::Char16, &globalData, codeBlock, compileMode);
/* Unset iff the Yarr JIT compilation was successful. */
if (!codeBlock.isFallBack())
return true;
}
codeBlock.setFallBack(true);
#endif
WTF::BumpPointerAllocator *bumpAlloc = cx->runtime()->getBumpPointerAllocator(cx);
if (!bumpAlloc) {
js_ReportOutOfMemory(cx);
return false;
}
bytecode = byteCompile(yarrPattern, bumpAlloc).get();
return true;
}
bool
RegExpShared::compileIfNecessary(JSContext *cx)
{
if (hasCode() || hasBytecode())
return true;
return compile(cx, false);
}
bool
RegExpShared::compileMatchOnlyIfNecessary(JSContext *cx)
{
if (hasMatchOnlyCode() || hasBytecode())
return true;
return compile(cx, true);
}
RegExpRunStatus
RegExpShared::execute(JSContext *cx, const jschar *chars, size_t length,
size_t *lastIndex, MatchPairs &matches)
{
TRACK_MEMORY_SCOPE("Javascript");
/* Compile the code at point-of-use. */
if (!compileIfNecessary(cx))
return RegExpRunStatus_Error;
/* Ensure sufficient memory for output vector. */
if (!matches.initArray(pairCount()))
return RegExpRunStatus_Error;
/*
* |displacement| emulates sticky mode by matching from this offset
* into the char buffer and subtracting the delta off at the end.
*/
size_t origLength = length;
size_t start = *lastIndex;
size_t displacement = 0;
if (sticky()) {
displacement = start;
chars += displacement;
length -= displacement;
start = 0;
}
unsigned *outputBuf = matches.rawBuf();
unsigned result;
#if ENABLE_YARR_JIT
if (codeBlock.isFallBack())
result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, outputBuf);
else
result = codeBlock.execute(chars, start, length, (int *)outputBuf).start;
#else
result = JSC::Yarr::interpret(cx, bytecode, chars, length, start, outputBuf);
#endif
if (result == JSC::Yarr::offsetNoMatch)
return RegExpRunStatus_Success_NotFound;
matches.displace(displacement);
matches.checkAgainst(origLength);
*lastIndex = matches[0].limit;
return RegExpRunStatus_Success;
}
RegExpRunStatus
RegExpShared::executeMatchOnly(JSContext *cx, const jschar *chars, size_t length,
size_t *lastIndex, MatchPair &match)
{
/* These chars may be inline in a string. See bug 846011. */
SkipRoot skipChars(cx, &chars);
/* Compile the code at point-of-use. */
if (!compileMatchOnlyIfNecessary(cx))
return RegExpRunStatus_Error;
#ifdef DEBUG
const size_t origLength = length;
#endif
size_t start = *lastIndex;
size_t displacement = 0;
if (sticky()) {
displacement = start;
chars += displacement;
length -= displacement;
start = 0;
}
#if ENABLE_YARR_JIT
if (!codeBlock.isFallBack()) {
MatchResult result = codeBlock.execute(chars, start, length);
if (!result)
return RegExpRunStatus_Success_NotFound;
match = MatchPair(result.start, result.end);
match.displace(displacement);
*lastIndex = match.limit;
return RegExpRunStatus_Success;
}
#endif
/*
* The JIT could not be used, so fall back to the Yarr interpreter.
* Unfortunately, the interpreter does not have a MatchOnly mode, so a
* temporary output vector must be provided.
*/
JS_ASSERT(hasBytecode());
ScopedMatchPairs matches(&cx->tempLifoAlloc());
if (!matches.initArray(pairCount()))
return RegExpRunStatus_Error;
unsigned result =
JSC::Yarr::interpret(cx, bytecode, chars, length, start, matches.rawBuf());
if (result == JSC::Yarr::offsetNoMatch)
return RegExpRunStatus_Success_NotFound;
match = MatchPair(result, matches[0].limit);
match.displace(displacement);
#ifdef DEBUG
matches.displace(displacement);
matches.checkAgainst(origLength);
#endif
*lastIndex = match.limit;
return RegExpRunStatus_Success;
}
/* RegExpCompartment */
RegExpCompartment::RegExpCompartment(JSRuntime *rt)
: map_(rt), inUse_(rt)
{}
RegExpCompartment::~RegExpCompartment()
{
JS_ASSERT(map_.empty());
JS_ASSERT(inUse_.empty());
}
bool
RegExpCompartment::init(JSContext *cx)
{
if (!map_.init(0) || !inUse_.init(0)) {
if (cx)
js_ReportOutOfMemory(cx);
return false;
}
return true;
}
/* See the comment on RegExpShared lifetime in RegExpObject.h. */
void
RegExpCompartment::sweep(JSRuntime *rt)
{
#ifdef DEBUG
for (Map::Range r = map_.all(); !r.empty(); r.popFront())
JS_ASSERT(inUse_.has(r.front().value));
#endif
map_.clear();
for (PendingSet::Enum e(inUse_); !e.empty(); e.popFront()) {
RegExpShared *shared = e.front();
if (shared->activeUseCount == 0 && shared->gcNumberWhenUsed < rt->gcStartNumber) {
js_delete(shared);
e.removeFront();
}
}
}
bool
RegExpCompartment::get(JSContext *cx, JSAtom *source, RegExpFlag flags, RegExpGuard *g)
{
Key key(source, flags);
Map::AddPtr p = map_.lookupForAdd(key);
if (p) {
g->init(*p->value);
return true;
}
ScopedJSDeletePtr<RegExpShared> shared(cx->new_<RegExpShared>(cx->runtime(), source, flags));
if (!shared)
return false;
/* Add to RegExpShared sharing hashmap. */
if (!map_.add(p, key, shared)) {
js_ReportOutOfMemory(cx);
return false;
}
/* Add to list of all RegExpShared objects in this RegExpCompartment. */
if (!inUse_.put(shared)) {
map_.remove(key);
js_ReportOutOfMemory(cx);
return false;
}
/* Since error deletes |shared|, only guard |shared| on success. */
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(JSMallocSizeOfFun mallocSizeOf)
{
size_t n = 0;
n += map_.sizeOfExcludingThis(mallocSizeOf);
n += inUse_.sizeOfExcludingThis(mallocSizeOf);
return n;
}
/* Functions */
JSObject *
js::CloneRegExpObject(JSContext *cx, JSObject *obj_, JSObject *proto_)
{
RegExpObjectBuilder builder(cx);
Rooted<RegExpObject*> regex(cx, &obj_->as<RegExpObject>());
Rooted<RegExpObject*> proto(cx, &proto_->as<RegExpObject>());
return builder.clone(regex, proto);
}
bool
js::ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut)
{
size_t n = flagStr->length();
const jschar *s = flagStr->getChars(cx);
if (!s)
return false;
*flagsOut = RegExpFlag(0);
for (size_t i = 0; i < n; i++) {
#define HANDLE_FLAG(name_) \
JS_BEGIN_MACRO \
if (*flagsOut & (name_)) \
goto bad_flag; \
*flagsOut = RegExpFlag(*flagsOut | (name_)); \
JS_END_MACRO
switch (s[i]) {
case 'i': HANDLE_FLAG(IgnoreCaseFlag); break;
case 'g': HANDLE_FLAG(GlobalFlag); break;
case 'm': HANDLE_FLAG(MultilineFlag); break;
case 'y': HANDLE_FLAG(StickyFlag); break;
default:
bad_flag:
{
char charBuf[2];
charBuf[0] = char(s[i]);
charBuf[1] = '\0';
JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, js_GetErrorMessage, NULL,
JSMSG_BAD_REGEXP_FLAG, charBuf);
return false;
}
}
#undef HANDLE_FLAG
}
return true;
}
template<XDRMode mode>
bool
js::XDRScriptRegExpObject(XDRState<mode> *xdr, HeapPtrObject *objp)
{
/* NB: Keep this in sync with CloneScriptRegExpObject. */
RootedAtom source(xdr->cx());
uint32_t flagsword = 0;
if (mode == XDR_ENCODE) {
JS_ASSERT(objp);
RegExpObject &reobj = (*objp)->as<RegExpObject>();
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::createNoStatics(xdr->cx(), source, flags, NULL);
if (!reobj)
return false;
objp->init(reobj);
}
return true;
}
template bool
js::XDRScriptRegExpObject(XDRState<XDR_ENCODE> *xdr, HeapPtrObject *objp);
template bool
js::XDRScriptRegExpObject(XDRState<XDR_DECODE> *xdr, HeapPtrObject *objp);
JSObject *
js::CloneScriptRegExpObject(JSContext *cx, RegExpObject &reobj)
{
/* NB: Keep this in sync with XDRScriptRegExpObject. */
RootedAtom source(cx, reobj.getSource());
return RegExpObject::createNoStatics(cx, source, reobj.getFlags(), NULL);
}