blob: 3047ba407c25b942aa4439ad2f247354723a287d [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 "mozilla/MemoryReporting.h"
#include "mozilla/PodOperations.h"
#include "jsstr.h"
#include "builtin/RegExp.h"
#include "frontend/TokenStream.h"
#include "irregexp/RegExpParser.h"
#include "vm/MatchPairs.h"
#include "vm/RegExpStatics.h"
#include "vm/StringBuffer.h"
#include "vm/TraceLogging.h"
#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);
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
MaybeTraceRegExpShared(JSContext* cx, RegExpShared* shared)
{
Zone* zone = cx->zone();
if (zone->needsIncrementalBarrier())
shared->trace(zone->barrierTracer());
}
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.
MaybeTraceRegExpShared(cx, shared);
g->init(*shared);
return true;
}
return createShared(cx, g);
}
/* 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()->isHeapBusy() &&
trc->isMarkingTracer() &&
!obj->asTenured().zone()->isPreservingCode())
{
obj->as<RegExpObject>().NativeObject::setPrivate(nullptr);
} else {
shared->trace(trc);
}
}
const Class RegExpObject::class_ = {
js_RegExp_str,
JSCLASS_HAS_PRIVATE |
JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) |
JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp),
nullptr, /* addProperty */
nullptr, /* delProperty */
nullptr, /* getProperty */
nullptr, /* setProperty */
nullptr, /* enumerate */
nullptr, /* resolve */
nullptr, /* mayResolve */
nullptr, /* finalize */
nullptr, /* call */
nullptr, /* hasInstance */
nullptr, /* construct */
RegExpObject::trace,
// ClassSpec
{
GenericCreateConstructor<js::regexp_construct, 2, gc::AllocKind::FUNCTION>,
CreateRegExpPrototype,
nullptr,
js::regexp_static_props,
js::regexp_methods,
js::regexp_properties
}
};
RegExpObject*
RegExpObject::create(ExclusiveContext* cx, RegExpStatics* res, const char16_t* chars, size_t length,
RegExpFlag flags, TokenStream* tokenStream, LifoAlloc& alloc)
{
RegExpFlag staticsFlags = res->getFlags();
return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream, alloc);
}
RegExpObject*
RegExpObject::createNoStatics(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 createNoStatics(cx, source, flags, tokenStream, alloc);
}
RegExpObject*
RegExpObject::createNoStatics(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))
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);
setGlobal(flags & GlobalFlag);
setIgnoreCase(flags & IgnoreCaseFlag);
setMultiline(flags & MultilineFlag);
setSticky(flags & StickyFlag);
}
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: returns the original 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 (sticky() && !sb.append('y'))
return nullptr;
return sb.finishString();
}
/* 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;
if (source)
TraceEdge(trc, &source, "RegExpShared source");
for (size_t i = 0; i < ArrayLength(compilationArray); i++) {
RegExpCompilation& compilation = compilationArray[i];
if (compilation.jitCode)
TraceEdge(trc, &compilation.jitCode, "RegExpShared code");
}
}
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, &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());
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)
{
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);
}
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);
}
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);
} else {
const char16_t* chars = input->twoByteChars(nogc);
result = irregexp::ExecuteCode(cx, code, chars, start, length, matches);
}
}
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().start().get();
result = irregexp::InterpretCode(cx, byteCode, chars, start, length, matches);
} else {
const char16_t* chars = inputChars.twoByteRange().start().get();
result = irregexp::InterpretCode(cx, byteCode, chars, start, length, matches);
}
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)
{}
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->getTaggedProto());
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;
}
void
RegExpCompartment::sweep(JSRuntime* rt)
{
if (!set_.initialized())
return;
for (Set::Enum e(set_); !e.empty(); e.popFront()) {
RegExpShared* shared = e.front();
// 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 = shared->marked() &&
IsMarked(&shared->source);
for (size_t i = 0; i < ArrayLength(shared->compilationArray); i++) {
RegExpShared::RegExpCompilation& compilation = shared->compilationArray[i];
if (compilation.jitCode &&
IsAboutToBeFinalized(&compilation.jitCode))
{
keep = false;
}
}
MOZ_ASSERT(rt->isHeapMajorCollecting());
if (keep || rt->gc.isHeapCompacting()) {
shared->clearMarked();
} else {
js_delete(shared);
e.removeFront();
}
}
if (matchResultTemplateObject_ &&
IsAboutToBeFinalized(&matchResultTemplateObject_))
{
matchResultTemplateObject_.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).
MaybeTraceRegExpShared(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.
MaybeTraceRegExpShared(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());
// Check that the RegExpShared for |regex| is okay to reuse in the clone.
RegExpStatics* currentStatics = regex->getProto()->global().getRegExpStatics(cx);
if (!currentStatics)
return nullptr;
RegExpFlag origFlags = regex->getFlags();
RegExpFlag staticsFlags = currentStatics->getFlags();
if ((origFlags & staticsFlags) != staticsFlags) {
// If |currentStatics| provides additional flags, we'll have to use a
// new |RegExpShared|.
clone->initAndZeroLastIndex(source, RegExpFlag(origFlags | staticsFlags), cx);
} else {
// Otherwise we can use |regexp|'s RegExpShared. Initialize using its
// flags and associate it with the clone.
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;
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) {
char charBuf[2];
charBuf[0] = char(lastParsed);
charBuf[1] = '\0';
JS_ReportErrorFlagsAndNumber(cx, JSREPORT_ERROR, GetErrorMessage, nullptr,
JSMSG_BAD_REGEXP_FLAG, charBuf);
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::createNoStatics(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::createNoStatics(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);
}