Bug 724748 - simplify RegExpShared lifetime management (r=cdleary)

This commit is contained in:
Luke Wagner 2012-02-07 12:34:29 -08:00
parent 11d130df7f
commit 330f1dd2d0
18 changed files with 893 additions and 1154 deletions

View File

@ -38,7 +38,7 @@
*
* ***** END LICENSE BLOCK ***** */
#include "jsinfer.h"
#include "jscntxt.h"
#include "builtin/RegExp.h"
@ -126,13 +126,13 @@ CreateRegExpMatchResult(JSContext *cx, JSString *input, const jschar *chars, siz
template <class T>
bool
ExecuteRegExpImpl(JSContext *cx, RegExpStatics *res, T *re, JSLinearString *input,
ExecuteRegExpImpl(JSContext *cx, RegExpStatics *res, T &re, JSLinearString *input,
const jschar *chars, size_t length,
size_t *lastIndex, RegExpExecType type, Value *rval)
{
LifoAllocScope allocScope(&cx->tempLifoAlloc());
MatchPairs *matchPairs = NULL;
RegExpRunStatus status = re->execute(cx, chars, length, lastIndex, allocScope, &matchPairs);
RegExpRunStatus status = re.execute(cx, chars, length, lastIndex, &matchPairs);
switch (status) {
case RegExpRunStatus_Error:
@ -159,15 +159,15 @@ ExecuteRegExpImpl(JSContext *cx, RegExpStatics *res, T *re, JSLinearString *inpu
}
bool
js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, const RegExpMatcher &matcher, JSLinearString *input,
js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpShared &shared, JSLinearString *input,
const jschar *chars, size_t length,
size_t *lastIndex, RegExpExecType type, Value *rval)
{
return ExecuteRegExpImpl(cx, res, &matcher, input, chars, length, lastIndex, type, rval);
return ExecuteRegExpImpl(cx, res, shared, input, chars, length, lastIndex, type, rval);
}
bool
js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject *reobj, JSLinearString *input,
js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject &reobj, JSLinearString *input,
const jschar *chars, size_t length,
size_t *lastIndex, RegExpExecType type, Value *rval)
{
@ -175,8 +175,8 @@ js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject *reobj, JSLine
}
/* Note: returns the original if no escaping need be performed. */
static JSLinearString *
EscapeNakedForwardSlashes(JSContext *cx, JSLinearString *unescaped)
static JSAtom *
EscapeNakedForwardSlashes(JSContext *cx, JSAtom *unescaped)
{
size_t oldLen = unescaped->length();
const jschar *oldChars = unescaped->chars();
@ -203,7 +203,7 @@ EscapeNakedForwardSlashes(JSContext *cx, JSLinearString *unescaped)
return NULL;
}
return sb.empty() ? unescaped : sb.finishString();
return sb.empty() ? unescaped : sb.finishAtom();
}
/*
@ -230,25 +230,46 @@ CompileRegExpObject(JSContext *cx, RegExpObjectBuilder &builder, CallArgs args)
}
Value sourceValue = args[0];
/*
* If we get passed in an object whose internal [[Class]] property is
* "RegExp", return a new object with the same source/flags.
*/
if (IsObjectWithClass(sourceValue, ESClass_RegExp, cx)) {
/*
* If we get passed in a |RegExpObject| source we return a new
* object with the same source/flags.
*
* Note: the regexp static flags are not taken into consideration here.
* Beware, sourceObj may be a (transparent) proxy to a RegExp, so only
* use generic (proxyable) operations on sourceObj that do not assume
* sourceObj.isRegExp().
*/
JSObject &sourceObj = sourceValue.toObject();
if (args.length() >= 2 && !args[1].isUndefined()) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_NEWREGEXP_FLAGGED);
return false;
}
RegExpShared *shared = RegExpToShared(cx, sourceObj);
if (!shared)
/*
* Only extract the 'flags' out of sourceObj; do not reuse the
* RegExpShared since it may be from a different compartment.
*/
RegExpFlag flags;
{
RegExpShared *shared = RegExpToShared(cx, sourceObj);
if (!shared)
return false;
flags = shared->getFlags();
}
/*
* 'toSource' is a permanent read-only property, so this is equivalent
* to executing RegExpObject::getSource on the unwrapped object.
*/
Value v;
if (!sourceObj.getProperty(cx, cx->runtime->atomState.sourceAtom, &v))
return false;
shared->incref(cx);
RegExpObject *reobj = builder.build(AlreadyIncRefed<RegExpShared>(shared));
RegExpObject *reobj = builder.build(&v.toString()->asAtom(), flags);
if (!reobj)
return false;
@ -256,16 +277,17 @@ CompileRegExpObject(JSContext *cx, RegExpObjectBuilder &builder, CallArgs args)
return true;
}
JSLinearString *sourceStr;
JSAtom *source;
if (sourceValue.isUndefined()) {
sourceStr = cx->runtime->emptyString;
source = cx->runtime->emptyString;
} else {
/* Coerce to string and compile. */
JSString *str = ToString(cx, sourceValue);
if (!str)
return false;
sourceStr = str->ensureLinear(cx);
if (!sourceStr)
source = js_AtomizeString(cx, str);
if (!source)
return false;
}
@ -279,11 +301,11 @@ CompileRegExpObject(JSContext *cx, RegExpObjectBuilder &builder, CallArgs args)
return false;
}
JSLinearString *escapedSourceStr = EscapeNakedForwardSlashes(cx, sourceStr);
JSAtom *escapedSourceStr = EscapeNakedForwardSlashes(cx, source);
if (!escapedSourceStr)
return false;
if (!CheckRegExpSyntax(cx, escapedSourceStr))
if (!js::detail::RegExpCode::checkSyntax(cx, NULL, escapedSourceStr))
return false;
RegExpStatics *res = cx->regExpStatics();
@ -500,6 +522,39 @@ js_InitRegExpClass(JSContext *cx, JSObject *obj)
return proto;
}
static const jschar GreedyStarChars[] = {'.', '*'};
static inline bool
StartsWithGreedyStar(JSAtom *source)
{
return false;
#if 0
if (source->length() < 3)
return false;
const jschar *chars = source->chars();
return chars[0] == GreedyStarChars[0] &&
chars[1] == GreedyStarChars[1] &&
chars[2] != '?';
#endif
}
static inline RegExpShared *
GetSharedForGreedyStar(JSContext *cx, JSAtom *source, RegExpFlag flags)
{
if (RegExpShared *hit = cx->compartment->regExps.lookupHack(cx, source, flags))
return hit;
JSAtom *hackedSource = js_AtomizeChars(cx, source->chars() + ArrayLength(GreedyStarChars),
source->length() - ArrayLength(GreedyStarChars));
if (!hackedSource)
return NULL;
return cx->compartment->regExps.getHack(cx, source, hackedSource, flags);
}
/*
* ES5 15.10.6.2 (and 15.10.6.3, which calls 15.10.6.2).
*
@ -519,17 +574,16 @@ ExecuteRegExp(JSContext *cx, Native native, uintN argc, Value *vp)
RegExpObject &reobj = obj->asRegExp();
RegExpMatcher matcher(cx);
if (reobj.startsWithAtomizedGreedyStar()) {
if (!matcher.initWithTestOptimized(reobj))
return false;
} else {
RegExpShared *shared = reobj.getShared(cx);
if (!shared)
return false;
matcher.init(NeedsIncRef<RegExpShared>(shared));
}
RegExpShared *shared;
if (StartsWithGreedyStar(reobj.getSource()))
shared = GetSharedForGreedyStar(cx, reobj.getSource(), reobj.getFlags());
else
shared = reobj.getShared(cx);
if (!shared)
return false;
RegExpShared::Guard re(*shared);
RegExpStatics *res = cx->regExpStatics();
/* Step 2. */
@ -553,7 +607,7 @@ ExecuteRegExp(JSContext *cx, Native native, uintN argc, Value *vp)
return false;
/* Steps 6-7 (with sticky extension). */
if (!matcher.global() && !matcher.sticky())
if (!re->global() && !re->sticky())
i = 0;
/* Step 9a. */
@ -566,13 +620,13 @@ ExecuteRegExp(JSContext *cx, Native native, uintN argc, Value *vp)
/* Steps 8-21. */
RegExpExecType execType = (native == regexp_test) ? RegExpTest : RegExpExec;
size_t lastIndexInt(i);
if (!ExecuteRegExp(cx, res, matcher, linearInput, chars, length, &lastIndexInt, execType,
if (!ExecuteRegExp(cx, res, *re, linearInput, chars, length, &lastIndexInt, execType,
&args.rval())) {
return false;
}
/* Step 11 (with sticky extension). */
if (matcher.global() || (!args.rval().isNull() && matcher.sticky())) {
if (re->global() || (!args.rval().isNull() && re->sticky())) {
if (args.rval().isNull())
reobj.zeroLastIndex();
else

View File

@ -59,12 +59,12 @@ namespace js {
* |chars| and |length|.
*/
bool
ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject *reobj,
ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject &reobj,
JSLinearString *input, const jschar *chars, size_t length,
size_t *lastIndex, RegExpExecType type, Value *rval);
bool
ExecuteRegExp(JSContext *cx, RegExpStatics *res, const RegExpMatcher &matcher,
ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpShared &shared,
JSLinearString *input, const jschar *chars, size_t length,
size_t *lastIndex, RegExpExecType type, Value *rval);

View File

@ -702,7 +702,6 @@ JSRuntime::JSRuntime()
tempLifoAlloc(TEMP_LIFO_ALLOC_PRIMARY_CHUNK_SIZE),
execAlloc_(NULL),
bumpAlloc_(NULL),
reCache_(NULL),
nativeStackBase(0),
nativeStackQuota(0),
interpreterFrames(NULL),
@ -850,7 +849,6 @@ JSRuntime::~JSRuntime()
delete_<JSC::ExecutableAllocator>(execAlloc_);
delete_<WTF::BumpPointerAllocator>(bumpAlloc_);
JS_ASSERT(!reCache_);
#ifdef DEBUG
/* Don't hurt everyone in leaky ol' Mozilla with a fatal JS_ASSERT! */
@ -6361,7 +6359,7 @@ JS_ExecuteRegExp(JSContext *cx, JSObject *obj, JSObject *reobj, jschar *chars, s
CHECK_REQUEST(cx);
RegExpStatics *res = obj->asGlobal().getRegExpStatics();
return ExecuteRegExp(cx, res, &reobj->asRegExp(), NULL, chars, length,
return ExecuteRegExp(cx, res, reobj->asRegExp(), NULL, chars, length,
indexp, test ? RegExpTest : RegExpExec, rval);
}
@ -6393,7 +6391,7 @@ JS_ExecuteRegExpNoStatics(JSContext *cx, JSObject *obj, jschar *chars, size_t le
AssertNoGC(cx);
CHECK_REQUEST(cx);
return ExecuteRegExp(cx, NULL, &obj->asRegExp(), NULL, chars, length, indexp,
return ExecuteRegExp(cx, NULL, obj->asRegExp(), NULL, chars, length, indexp,
test ? RegExpTest : RegExpExec, rval);
}

View File

@ -148,23 +148,6 @@ JSRuntime::createBumpPointerAllocator(JSContext *cx)
return bumpAlloc_;
}
RegExpCache *
JSRuntime::createRegExpCache(JSContext *cx)
{
JS_ASSERT(!reCache_);
JS_ASSERT(cx->runtime == this);
RegExpCache *newCache = new_<RegExpCache>(this);
if (!newCache || !newCache->init()) {
js_ReportOutOfMemory(cx);
delete_<RegExpCache>(newCache);
return NULL;
}
reCache_ = newCache;
return reCache_;
}
JSScript *
js_GetCurrentScript(JSContext *cx)
{
@ -1170,9 +1153,6 @@ JSRuntime::purge(JSContext *cx)
/* FIXME: bug 506341 */
propertyCache.purge(cx);
delete_<RegExpCache>(reCache_);
reCache_ = NULL;
}
void

View File

@ -96,11 +96,6 @@ class InterpreterFrames;
class ScriptOpcodeCounts;
struct ScriptOpcodeCountsPair;
typedef HashMap<JSAtom *,
detail::RegExpCacheValue,
DefaultHasher<JSAtom *>,
RuntimeAllocPolicy> RegExpCache;
/*
* GetSrcNote cache to avoid O(n^2) growth in finding a source note for a
* given pc in a script. We use the script->code pointer to tag the cache,
@ -220,11 +215,9 @@ struct JSRuntime : js::RuntimeFriendFields
*/
JSC::ExecutableAllocator *execAlloc_;
WTF::BumpPointerAllocator *bumpAlloc_;
js::RegExpCache *reCache_;
JSC::ExecutableAllocator *createExecutableAllocator(JSContext *cx);
WTF::BumpPointerAllocator *createBumpPointerAllocator(JSContext *cx);
js::RegExpCache *createRegExpCache(JSContext *cx);
public:
JSC::ExecutableAllocator *getExecutableAllocator(JSContext *cx) {
@ -233,12 +226,6 @@ struct JSRuntime : js::RuntimeFriendFields
WTF::BumpPointerAllocator *getBumpPointerAllocator(JSContext *cx) {
return bumpAlloc_ ? bumpAlloc_ : createBumpPointerAllocator(cx);
}
js::RegExpCache *maybeRegExpCache() {
return reCache_;
}
js::RegExpCache *getRegExpCache(JSContext *cx) {
return reCache_ ? reCache_ : createRegExpCache(cx);
}
/* Base address of the native stack for the current thread. */
uintptr_t nativeStackBase;

View File

@ -85,6 +85,7 @@ JSCompartment::JSCompartment(JSRuntime *rt)
#ifdef JS_METHODJIT
jaegerCompartment_(NULL),
#endif
regExps(rt),
propertyTree(thisForCtor()),
emptyTypeObject(NULL),
debugModeBits(rt->debugMode ? DebugFromC : 0),
@ -121,6 +122,9 @@ JSCompartment::init(JSContext *cx)
if (!crossCompartmentWrappers.init())
return false;
if (!regExps.init(cx))
return false;
if (!scriptFilenameTable.init())
return false;
@ -549,6 +553,7 @@ void
JSCompartment::purge(JSContext *cx)
{
arenas.purge();
regExps.purge();
dtoaCache.purge();
/*

View File

@ -50,6 +50,7 @@
#include "jsobj.h"
#include "jsscope.h"
#include "vm/GlobalObject.h"
#include "vm/RegExpObject.h"
#ifdef _MSC_VER
#pragma warning(push)
@ -244,6 +245,8 @@ struct JSCompartment
size_t sizeOfMjitCode() const;
#endif
js::RegExpCompartment regExps;
size_t sizeOfShapeTable(JSMallocSizeOfFun mallocSizeOf);
void sizeOfTypeInferenceData(JSContext *cx, JS::TypeInferenceSizes *stats,
JSMallocSizeOfFun mallocSizeOf);

View File

@ -2882,7 +2882,7 @@ BEGIN_CASE(JSOP_REGEXP)
JSObject *proto = regs.fp()->scopeChain().global().getOrCreateRegExpPrototype(cx);
if (!proto)
goto error;
JSObject *obj = js_CloneRegExpObject(cx, script->getRegExp(index), proto);
JSObject *obj = CloneRegExpObject(cx, script->getRegExp(index), proto);
if (!obj)
goto error;
PUSH_OBJECT(*obj);

View File

@ -124,18 +124,12 @@ struct ArgumentsData;
struct Class;
class RegExpObject;
class RegExpMatcher;
class RegExpObjectBuilder;
class RegExpShared;
class RegExpStatics;
class MatchPairs;
namespace detail {
class RegExpCode;
class RegExpCacheValue;
} /* namespace detail */
namespace detail { class RegExpCode; }
enum RegExpFlag
{

View File

@ -2820,7 +2820,7 @@ ASTSerializer::literal(ParseNode *pn, Value *dst)
if (!js_GetClassPrototype(cx, &cx->fp()->scopeChain(), JSProto_RegExp, &proto))
return false;
JSObject *re2 = js_CloneRegExpObject(cx, re1, proto);
JSObject *re2 = CloneRegExpObject(cx, re1, proto);
if (!re2)
return false;

View File

@ -1332,10 +1332,10 @@ str_trimRight(JSContext *cx, uintN argc, Value *vp)
/* Result of a successfully performed flat match. */
class FlatMatch
{
JSLinearString *patstr;
const jschar *pat;
size_t patlen;
int32_t match_;
JSAtom *patstr;
const jschar *pat;
size_t patlen;
int32_t match_;
friend class RegExpGuard;
@ -1351,6 +1351,30 @@ class FlatMatch
int32_t match() const { return match_; }
};
static inline bool
IsRegExpMetaChar(jschar c)
{
switch (c) {
/* Taken from the PatternCharacter production in 15.10.1. */
case '^': case '$': case '\\': case '.': case '*': case '+':
case '?': case '(': case ')': case '[': case ']': case '{':
case '}': case '|':
return true;
default:
return false;
}
}
static inline bool
HasRegExpMetaChars(const jschar *chars, size_t length)
{
for (size_t i = 0; i < length; ++i) {
if (IsRegExpMetaChar(chars[i]))
return true;
}
return false;
}
/*
* RegExpGuard factors logic out of String regexp operations.
*
@ -1362,9 +1386,8 @@ class RegExpGuard
RegExpGuard(const RegExpGuard &) MOZ_DELETE;
void operator=(const RegExpGuard &) MOZ_DELETE;
JSContext *cx;
RegExpMatcher matcher;
FlatMatch fm;
RegExpShared::Guard re_;
FlatMatch fm;
/*
* Upper bound on the number of characters we are willing to potentially
@ -1372,7 +1395,9 @@ class RegExpGuard
*/
static const size_t MAX_FLAT_PAT_LEN = 256;
static JSLinearString *flattenPattern(JSContext *cx, JSLinearString *patstr) {
static JSAtom *
flattenPattern(JSContext *cx, JSAtom *patstr)
{
StringBuffer sb(cx);
if (!sb.reserve(patstr->length()))
return NULL;
@ -1389,30 +1414,31 @@ class RegExpGuard
return NULL;
}
}
return sb.finishString();
return sb.finishAtom();
}
public:
explicit RegExpGuard(JSContext *cx) : cx(cx), matcher(cx) {}
~RegExpGuard() {}
RegExpGuard() {}
/* init must succeed in order to call tryFlatMatch or normalizeRegExp. */
bool
init(CallArgs args, bool convertVoid = false)
bool init(JSContext *cx, CallArgs args, bool convertVoid = false)
{
if (args.length() != 0 && IsObjectWithClass(args[0], ESClass_RegExp, cx)) {
RegExpShared *shared = RegExpToShared(cx, args[0].toObject());
if (!shared)
return false;
matcher.init(NeedsIncRef<RegExpShared>(shared));
re_.init(*shared);
} else {
if (convertVoid && (args.length() == 0 || args[0].isUndefined())) {
fm.patstr = cx->runtime->emptyString;
return true;
}
fm.patstr = ArgToRootedString(cx, args, 0);
JSString *arg = ArgToRootedString(cx, args, 0);
if (!arg)
return false;
fm.patstr = js_AtomizeString(cx, arg);
if (!fm.patstr)
return false;
}
@ -1433,7 +1459,7 @@ class RegExpGuard
tryFlatMatch(JSContext *cx, JSString *textstr, uintN optarg, uintN argc,
bool checkMetaChars = true)
{
if (matcher.initialized())
if (re_.initialized())
return NULL;
fm.pat = fm.patstr->chars();
@ -1463,41 +1489,40 @@ class RegExpGuard
}
/* If the pattern is not already a regular expression, make it so. */
const RegExpMatcher *
normalizeRegExp(bool flat, uintN optarg, CallArgs args)
bool normalizeRegExp(JSContext *cx, bool flat, uintN optarg, CallArgs args)
{
if (matcher.initialized())
return &matcher;
if (re_.initialized())
return true;
/* Build RegExp from pattern string. */
JSString *opt;
if (optarg < args.length()) {
opt = ToString(cx, args[optarg]);
if (!opt)
return NULL;
return false;
} else {
opt = NULL;
}
JSLinearString *patstr;
JSAtom *patstr;
if (flat) {
patstr = flattenPattern(cx, fm.patstr);
if (!patstr)
return NULL;
return false;
} else {
patstr = fm.patstr;
}
JS_ASSERT(patstr);
if (!matcher.init(patstr, opt))
return NULL;
RegExpShared *re = cx->compartment->regExps.get(cx, patstr, opt);
if (!re)
return false;
return &matcher;
re_.init(*re);
return true;
}
#if DEBUG
bool matcherInitialized() const { return matcher.initialized(); }
#endif
RegExpShared &regExp() { return *re_; }
};
/* ExecuteRegExp indicates success in two ways, based on the 'test' flag. */
@ -1525,7 +1550,7 @@ enum MatchControlFlags {
/* Factor out looping and matching logic. */
static bool
DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, const RegExpMatcher &matcher,
DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, RegExpShared &re,
DoMatchCallback callback, void *data, MatchControlFlags flags, Value *rval)
{
JSLinearString *linearStr = str->ensureLinear(cx);
@ -1535,10 +1560,10 @@ DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, const RegExpMatcher &m
const jschar *chars = linearStr->chars();
size_t length = linearStr->length();
if (matcher.global()) {
if (re.global()) {
RegExpExecType type = (flags & TEST_GLOBAL_BIT) ? RegExpTest : RegExpExec;
for (size_t count = 0, i = 0, length = str->length(); i <= length; ++count) {
if (!ExecuteRegExp(cx, res, matcher, linearStr, chars, length, &i, type, rval))
if (!ExecuteRegExp(cx, res, re, linearStr, chars, length, &i, type, rval))
return false;
if (!Matched(type, *rval))
break;
@ -1551,7 +1576,7 @@ DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, const RegExpMatcher &m
RegExpExecType type = (flags & TEST_SINGLE_BIT) ? RegExpTest : RegExpExec;
bool callbackOnSingle = !!(flags & CALLBACK_ON_SINGLE_BIT);
size_t i = 0;
if (!ExecuteRegExp(cx, res, matcher, linearStr, chars, length, &i, type, rval))
if (!ExecuteRegExp(cx, res, re, linearStr, chars, length, &i, type, rval))
return false;
if (callbackOnSingle && Matched(type, *rval) && !callback(cx, res, 0, data))
return false;
@ -1613,29 +1638,28 @@ js::str_match(JSContext *cx, uintN argc, Value *vp)
if (!str)
return false;
RegExpGuard g(cx);
if (!g.init(args, true))
RegExpGuard g;
if (!g.init(cx, args, true))
return false;
if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, args.length()))
return BuildFlatMatchArray(cx, str, *fm, &args);
/* Return if there was an error in tryFlatMatch. */
if (cx->isExceptionPending()) /* from tryFlatMatch */
if (cx->isExceptionPending())
return false;
const RegExpMatcher *matcher = g.normalizeRegExp(false, 1, args);
if (!matcher)
if (!g.normalizeRegExp(cx, false, 1, args))
return false;
JSObject *array = NULL;
MatchArgType arg = &array;
RegExpStatics *res = cx->regExpStatics();
Value rval;
if (!DoMatch(cx, res, str, *matcher, MatchCallback, arg, MATCH_ARGS, &rval))
if (!DoMatch(cx, res, str, g.regExp(), MatchCallback, arg, MATCH_ARGS, &rval))
return false;
if (matcher->global())
if (g.regExp().global())
args.rval() = ObjectOrNullValue(array);
else
args.rval() = rval;
@ -1650,8 +1674,8 @@ js::str_search(JSContext *cx, uintN argc, Value *vp)
if (!str)
return false;
RegExpGuard g(cx);
if (!g.init(args, true))
RegExpGuard g;
if (!g.init(cx, args, true))
return false;
if (const FlatMatch *fm = g.tryFlatMatch(cx, str, 1, args.length())) {
args.rval() = Int32Value(fm->match());
@ -1661,8 +1685,7 @@ js::str_search(JSContext *cx, uintN argc, Value *vp)
if (cx->isExceptionPending()) /* from tryFlatMatch */
return false;
const RegExpMatcher *matcher = g.normalizeRegExp(false, 1, args);
if (!matcher)
if (!g.normalizeRegExp(cx, false, 1, args))
return false;
JSLinearString *linearStr = str->ensureLinear(cx);
@ -1676,7 +1699,7 @@ js::str_search(JSContext *cx, uintN argc, Value *vp)
/* Per ECMAv5 15.5.4.12 (5) The last index property is ignored and left unchanged. */
size_t i = 0;
Value result;
if (!ExecuteRegExp(cx, res, *matcher, linearStr, chars, length, &i, RegExpTest, &result))
if (!ExecuteRegExp(cx, res, g.regExp(), linearStr, chars, length, &i, RegExpTest, &result))
return false;
if (result.isTrue())
@ -1689,7 +1712,7 @@ js::str_search(JSContext *cx, uintN argc, Value *vp)
struct ReplaceData
{
ReplaceData(JSContext *cx)
: g(cx), sb(cx)
: sb(cx)
{}
JSString *str; /* 'this' parameter object as a string */
@ -2117,16 +2140,17 @@ BuildDollarReplacement(JSContext *cx, JSString *textstrArg, JSLinearString *reps
static inline bool
str_replace_regexp(JSContext *cx, CallArgs args, ReplaceData &rdata)
{
const RegExpMatcher *matcher = rdata.g.normalizeRegExp(true, 2, args);
if (!matcher)
if (!rdata.g.normalizeRegExp(cx, true, 2, args))
return false;
rdata.leftIndex = 0;
rdata.calledBack = false;
RegExpStatics *res = cx->regExpStatics();
RegExpShared &re = rdata.g.regExp();
Value tmp;
if (!DoMatch(cx, res, rdata.str, *matcher, ReplaceRegExpCallback, &rdata, REPLACE_ARGS, &tmp))
if (!DoMatch(cx, res, rdata.str, re, ReplaceRegExpCallback, &rdata, REPLACE_ARGS, &tmp))
return false;
if (!rdata.calledBack) {
@ -2211,7 +2235,7 @@ js::str_replace(JSContext *cx, uintN argc, Value *vp)
if (!rdata.str)
return false;
if (!rdata.g.init(args))
if (!rdata.g.init(cx, args))
return false;
/* Extract replacement string/function. */
@ -2283,7 +2307,6 @@ js::str_replace(JSContext *cx, uintN argc, Value *vp)
if (!fm) {
if (cx->isExceptionPending()) /* oom in RopeMatch in tryFlatMatch */
return false;
JS_ASSERT_IF(!rdata.g.matcherInitialized(), args.length() > ReplaceOptArg);
return str_replace_regexp(cx, args, rdata);
}
@ -2459,25 +2482,27 @@ SplitHelper(JSContext *cx, JSLinearString *str, uint32_t limit, Matcher splitMat
/*
* The SplitMatch operation from ES5 15.5.4.14 is implemented using different
* matchers for regular expression and string separators.
* paths for regular expression and string separators.
*
* The algorithm differs from the spec in that the matchers return the next
* index at which a match happens.
* The algorithm differs from the spec in that the we return the next index at
* which a match happens.
*/
class SplitRegExpMatcher {
class SplitRegExpMatcher
{
RegExpShared &re;
RegExpStatics *res;
RegExpMatcher &matcher;
public:
static const bool returnsCaptures = true;
SplitRegExpMatcher(RegExpMatcher &matcher, RegExpStatics *res) : res(res), matcher(matcher) {}
SplitRegExpMatcher(RegExpShared &re, RegExpStatics *res) : re(re), res(res) {}
inline bool operator()(JSContext *cx, JSLinearString *str, size_t index,
SplitMatchResult *result) {
static const bool returnsCaptures = true;
bool operator()(JSContext *cx, JSLinearString *str, size_t index, SplitMatchResult *result)
{
Value rval = UndefinedValue();
const jschar *chars = str->chars();
size_t length = str->length();
if (!ExecuteRegExp(cx, res, matcher, str, chars, length, &index, RegExpTest, &rval))
if (!ExecuteRegExp(cx, res, re, str, chars, length, &index, RegExpTest, &rval))
return false;
if (!rval.isTrue()) {
result->setFailure();
@ -2491,19 +2516,21 @@ class SplitRegExpMatcher {
}
};
class SplitStringMatcher {
class SplitStringMatcher
{
const jschar *sepChars;
size_t sepLength;
public:
static const bool returnsCaptures = false;
SplitStringMatcher(JSLinearString *sep) {
sepChars = sep->chars();
sepLength = sep->length();
}
inline bool operator()(JSContext *cx, JSLinearString *str, size_t index,
SplitMatchResult *res) {
static const bool returnsCaptures = false;
bool operator()(JSContext *cx, JSLinearString *str, size_t index, SplitMatchResult *res)
{
JS_ASSERT(index == 0 || index < str->length());
const jschar *chars = str->chars();
jsint match = StringMatch(chars + index, str->length() - index, sepChars, sepLength);
@ -2543,7 +2570,7 @@ js::str_split(JSContext *cx, uintN argc, Value *vp)
}
/* Step 8. */
RegExpMatcher matcher(cx);
RegExpShared::Guard re;
JSLinearString *sepstr = NULL;
bool sepUndefined = (args.length() == 0 || args[0].isUndefined());
if (!sepUndefined) {
@ -2551,7 +2578,7 @@ js::str_split(JSContext *cx, uintN argc, Value *vp)
RegExpShared *shared = RegExpToShared(cx, args[0].toObject());
if (!shared)
return false;
matcher.init(NeedsIncRef<RegExpShared>(shared));
re.init(*shared);
} else {
sepstr = ArgToRootedString(cx, args, 0);
if (!sepstr)
@ -2585,13 +2612,10 @@ js::str_split(JSContext *cx, uintN argc, Value *vp)
/* Steps 11-15. */
JSObject *aobj;
if (!matcher.initialized()) {
// NB: sepstr is anchored through its storage in args[0].
if (!re.initialized())
aobj = SplitHelper(cx, strlin, limit, SplitStringMatcher(sepstr), type);
} else {
aobj = SplitHelper(cx, strlin, limit,
SplitRegExpMatcher(matcher, cx->regExpStatics()), type);
}
else
aobj = SplitHelper(cx, strlin, limit, SplitRegExpMatcher(*re, cx->regExpStatics()), type);
if (!aobj)
return false;

View File

@ -192,96 +192,6 @@ class AutoScopedAssign
~AutoScopedAssign() { *addr = old; }
};
template <class RefCountable>
class AlreadyIncRefed
{
typedef RefCountable *****ConvertibleToBool;
RefCountable *obj;
public:
explicit AlreadyIncRefed(RefCountable *obj = NULL) : obj(obj) {}
bool null() const { return obj == NULL; }
operator ConvertibleToBool() const { return (ConvertibleToBool)obj; }
RefCountable *operator->() const { JS_ASSERT(!null()); return obj; }
RefCountable &operator*() const { JS_ASSERT(!null()); return *obj; }
RefCountable *get() const { return obj; }
};
template <class RefCountable>
class NeedsIncRef
{
typedef RefCountable *****ConvertibleToBool;
RefCountable *obj;
public:
explicit NeedsIncRef(RefCountable *obj = NULL) : obj(obj) {}
bool null() const { return obj == NULL; }
operator ConvertibleToBool() const { return (ConvertibleToBool)obj; }
RefCountable *operator->() const { JS_ASSERT(!null()); return obj; }
RefCountable &operator*() const { JS_ASSERT(!null()); return *obj; }
RefCountable *get() const { return obj; }
};
template <class RefCountable>
class AutoRefCount
{
typedef RefCountable *****ConvertibleToBool;
JSContext *const cx;
RefCountable *obj;
AutoRefCount(const AutoRefCount &other) MOZ_DELETE;
void operator=(const AutoRefCount &other) MOZ_DELETE;
public:
explicit AutoRefCount(JSContext *cx)
: cx(cx), obj(NULL)
{}
AutoRefCount(JSContext *cx, NeedsIncRef<RefCountable> aobj)
: cx(cx), obj(aobj.get())
{
if (obj)
obj->incref(cx);
}
AutoRefCount(JSContext *cx, AlreadyIncRefed<RefCountable> aobj)
: cx(cx), obj(aobj.get())
{}
~AutoRefCount() {
if (obj)
obj->decref(cx);
}
void reset(NeedsIncRef<RefCountable> aobj) {
if (obj)
obj->decref(cx);
obj = aobj.get();
if (obj)
obj->incref(cx);
}
void reset(AlreadyIncRefed<RefCountable> aobj) {
if (obj)
obj->decref(cx);
obj = aobj.get();
}
bool null() const { return obj == NULL; }
operator ConvertibleToBool() const { return (ConvertibleToBool)obj; }
RefCountable *operator->() const { JS_ASSERT(!null()); return obj; }
RefCountable &operator*() const { JS_ASSERT(!null()); return *obj; }
RefCountable *get() const { return obj; }
};
template <class T>
JS_ALWAYS_INLINE static void
PodZero(T *t)

View File

@ -226,7 +226,7 @@ JS_XDRFindClassById(JSXDRState *xdr, uint32_t id);
* and saved versions. If deserialization fails, the data should be
* invalidated if possible.
*/
#define JSXDR_BYTECODE_VERSION (0xb973c0de - 106)
#define JSXDR_BYTECODE_VERSION (0xb973c0de - 107)
/*
* Library-private functions.

View File

@ -6939,10 +6939,10 @@ mjit::Compiler::jsop_regexp()
}
/*
* Force creation of the RegExpPrivate in the script's RegExpObject
* Force creation of the RegExpShared in the script's RegExpObject
* so that we grab it in the getNewObject template copy. Note that
* JIT code is discarded on every GC, which permits us to burn in
* the pointer to the RegExpPrivate refcount.
* the pointer to the RegExpShared.
*/
if (!reobj->getShared(cx))
return false;
@ -6956,10 +6956,6 @@ mjit::Compiler::jsop_regexp()
stubcc.masm.move(ImmPtr(obj), Registers::ArgReg1);
OOL_STUBCALL(stubs::RegExp, REJOIN_FALLTHROUGH);
/* Bump the refcount on the wrapped RegExp. */
size_t *refcount = reobj->addressOfPrivateRefCount();
masm.add32(Imm32(1), AbsoluteAddress(refcount));
frame.pushTypedPayload(JSVAL_TYPE_OBJECT, result);
stubcc.rejoin(Changes(1));

View File

@ -1104,7 +1104,7 @@ stubs::RegExp(VMFrame &f, JSObject *regex)
if (!proto)
THROW();
JS_ASSERT(proto);
JSObject *obj = js_CloneRegExpObject(f.cx, regex, proto);
JSObject *obj = CloneRegExpObject(f.cx, regex, proto);
if (!obj)
THROW();
f.regs.sp[0].setObject(*obj);

View File

@ -59,150 +59,25 @@ JSObject::asRegExp()
namespace js {
inline bool
IsRegExpMetaChar(jschar c)
inline RegExpShared &
RegExpObject::shared() const
{
switch (c) {
/* Taken from the PatternCharacter production in 15.10.1. */
case '^': case '$': case '\\': case '.': case '*': case '+':
case '?': case '(': case ')': case '[': case ']': case '{':
case '}': case '|':
return true;
default:
return false;
}
JS_ASSERT(JSObject::getPrivate() != NULL);
return *static_cast<RegExpShared *>(JSObject::getPrivate());
}
inline bool
HasRegExpMetaChars(const jschar *chars, size_t length)
inline RegExpShared *
RegExpObject::maybeShared()
{
for (size_t i = 0; i < length; ++i) {
if (IsRegExpMetaChar(chars[i]))
return true;
}
return false;
return static_cast<RegExpShared *>(JSObject::getPrivate());
}
inline bool
RegExpObject::startsWithAtomizedGreedyStar() const
inline RegExpShared *
RegExpObject::getShared(JSContext *cx)
{
JSLinearString *source = getSource();
if (!source->isAtom())
return false;
if (source->length() < 3)
return false;
const jschar *chars = source->chars();
return chars[0] == detail::GreedyStarChars[0] &&
chars[1] == detail::GreedyStarChars[1] &&
chars[2] != '?';
}
inline size_t *
RegExpObject::addressOfPrivateRefCount() const
{
return shared().addressOfRefCount();
}
inline RegExpObject *
RegExpObject::create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length,
RegExpFlag flags, TokenStream *tokenStream)
{
RegExpFlag staticsFlags = res->getFlags();
return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream);
}
inline RegExpObject *
RegExpObject::createNoStatics(JSContext *cx, const jschar *chars, size_t length,
RegExpFlag flags, TokenStream *tokenStream)
{
JSAtom *source = js_AtomizeChars(cx, chars, length);
if (!source)
return NULL;
return createNoStatics(cx, source, flags, tokenStream);
}
inline RegExpObject *
RegExpObject::createNoStatics(JSContext *cx, JSAtom *source, RegExpFlag flags,
TokenStream *tokenStream)
{
if (!RegExpCode::checkSyntax(cx, tokenStream, source))
return NULL;
RegExpObjectBuilder builder(cx);
return builder.build(source, flags);
}
inline void
RegExpObject::purge(JSContext *cx)
{
if (RegExpShared *shared = maybeShared()) {
shared->decref(cx);
JSObject::setPrivate(NULL);
}
}
inline void
RegExpObject::finalize(JSContext *cx)
{
purge(cx);
#ifdef DEBUG
JSObject::setPrivate((void *) 0x1); /* Non-null but still in the zero page. */
#endif
}
inline bool
RegExpObject::init(JSContext *cx, JSLinearString *source, RegExpFlag flags)
{
if (nativeEmpty()) {
if (isDelegate()) {
if (!assignInitialShape(cx))
return false;
} else {
Shape *shape = assignInitialShape(cx);
if (!shape)
return false;
EmptyShape::insertInitialShape(cx, shape, getProto());
}
JS_ASSERT(!nativeEmpty());
}
DebugOnly<JSAtomState *> atomState = &cx->runtime->atomState;
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->lastIndexAtom))->slot() == LAST_INDEX_SLOT);
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->sourceAtom))->slot() == SOURCE_SLOT);
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->globalAtom))->slot() == GLOBAL_FLAG_SLOT);
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->ignoreCaseAtom))->slot() ==
IGNORE_CASE_FLAG_SLOT);
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->multilineAtom))->slot() ==
MULTILINE_FLAG_SLOT);
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->stickyAtom))->slot() == STICKY_FLAG_SLOT);
JS_ASSERT(!maybeShared());
zeroLastIndex();
setSource(source);
setGlobal(flags & GlobalFlag);
setIgnoreCase(flags & IgnoreCaseFlag);
setMultiline(flags & MultilineFlag);
setSticky(flags & StickyFlag);
return true;
}
inline void
RegExpMatcher::init(NeedsIncRef<RegExpShared> shared)
{
JS_ASSERT(!shared_);
shared_.reset(shared);
}
inline bool
RegExpMatcher::init(JSLinearString *patstr, JSString *opt)
{
JS_ASSERT(!shared_);
shared_.reset(RegExpShared::create(cx_, patstr, opt, NULL));
return !!shared_;
if (RegExpShared *shared = maybeShared())
return shared;
return createShared(cx);
}
inline void
@ -224,7 +99,7 @@ RegExpObject::zeroLastIndex()
}
inline void
RegExpObject::setSource(JSLinearString *source)
RegExpObject::setSource(JSAtom *source)
{
setSlot(SOURCE_SLOT, StringValue(source));
}
@ -253,97 +128,6 @@ RegExpObject::setSticky(bool enabled)
setSlot(STICKY_FLAG_SLOT, BooleanValue(enabled));
}
/* RegExpShared inlines. */
inline bool
RegExpShared::cacheLookup(JSContext *cx, JSAtom *atom, RegExpFlag flags,
RegExpCacheKind targetKind,
AlreadyIncRefed<RegExpShared> *result)
{
RegExpCache *cache = cx->runtime->getRegExpCache(cx);
if (!cache)
return false;
if (RegExpCache::Ptr p = cache->lookup(atom)) {
RegExpCacheValue &cacheValue = p->value;
if (cacheValue.kind() == targetKind && cacheValue.shared().getFlags() == flags) {
cacheValue.shared().incref(cx);
*result = AlreadyIncRefed<RegExpShared>(&cacheValue.shared());
return true;
}
}
JS_ASSERT(result->null());
return true;
}
inline bool
RegExpShared::cacheInsert(JSContext *cx, JSAtom *atom, RegExpCacheKind kind,
RegExpShared &shared)
{
/*
* Note: allocation performed since lookup may cause a garbage collection,
* so we have to re-lookup the cache (and inside the cache) after the
* allocation is performed.
*/
RegExpCache *cache = cx->runtime->getRegExpCache(cx);
if (!cache)
return false;
if (RegExpCache::AddPtr addPtr = cache->lookupForAdd(atom)) {
/* We clobber existing entries with the same source (but different flags or kind). */
JS_ASSERT(addPtr->value.shared().getFlags() != shared.getFlags() ||
addPtr->value.kind() != kind);
addPtr->value.reset(shared, kind);
} else {
if (!cache->add(addPtr, atom, RegExpCacheValue(shared, kind))) {
js_ReportOutOfMemory(cx);
return false;
}
}
return true;
}
inline AlreadyIncRefed<RegExpShared>
RegExpShared::create(JSContext *cx, JSLinearString *source, RegExpFlag flags, TokenStream *ts)
{
typedef AlreadyIncRefed<RegExpShared> RetType;
/*
* We choose to only cache |RegExpShared|s who have atoms as
* sources, under the unverified premise that non-atoms will have a
* low hit rate (both hit ratio and absolute number of hits).
*/
bool cacheable = source->isAtom();
if (!cacheable)
return RetType(RegExpShared::createUncached(cx, source, flags, ts));
/*
* Refcount note: not all |RegExpShared|s are cached so we need to keep a
* refcount. The cache holds a "weak ref", where the |RegExpShared|'s
* deallocation decref will first cause it to remove itself from the cache.
*/
JSAtom *sourceAtom = &source->asAtom();
AlreadyIncRefed<RegExpShared> cached;
if (!cacheLookup(cx, sourceAtom, flags, detail::RegExpCache_ExecCapable, &cached))
return RetType(NULL);
if (cached)
return cached;
RegExpShared *shared = RegExpShared::createUncached(cx, source, flags, ts);
if (!shared)
return RetType(NULL);
if (!cacheInsert(cx, sourceAtom, detail::RegExpCache_ExecCapable, *shared))
return RetType(NULL);
return RetType(shared);
}
/* This function should be deleted once bad Android platforms phase out. See bug 604774. */
inline bool
detail::RegExpCode::isJITRuntimeEnabled(JSContext *cx)
@ -355,151 +139,6 @@ detail::RegExpCode::isJITRuntimeEnabled(JSContext *cx)
#endif
}
inline bool
detail::RegExpCode::compile(JSContext *cx, JSLinearString &pattern, TokenStream *ts,
uintN *parenCount, RegExpFlag flags)
{
#if ENABLE_YARR_JIT
/* Parse the pattern. */
ErrorCode yarrError;
YarrPattern yarrPattern(pattern, bool(flags & IgnoreCaseFlag), bool(flags & MultilineFlag),
&yarrError);
if (yarrError) {
reportYarrError(cx, ts, yarrError);
return false;
}
*parenCount = yarrPattern.m_numSubpatterns;
/*
* The YARR JIT compiler attempts to compile the parsed pattern. If
* it cannot, it informs us via |codeBlock.isFallBack()|, in which
* case we have to bytecode compile it.
*/
#ifdef JS_METHODJIT
if (isJITRuntimeEnabled(cx) && !yarrPattern.m_containsBackreferences) {
JSC::ExecutableAllocator *execAlloc = cx->runtime->getExecutableAllocator(cx);
if (!execAlloc) {
js_ReportOutOfMemory(cx);
return false;
}
JSGlobalData globalData(execAlloc);
jitCompile(yarrPattern, &globalData, codeBlock);
if (!codeBlock.isFallBack())
return true;
}
#endif
WTF::BumpPointerAllocator *bumpAlloc = cx->runtime->getBumpPointerAllocator(cx);
if (!bumpAlloc) {
js_ReportOutOfMemory(cx);
return false;
}
codeBlock.setFallBack(true);
byteCode = byteCompile(yarrPattern, bumpAlloc).get();
return true;
#else /* !defined(ENABLE_YARR_JIT) */
int error = 0;
compiled = jsRegExpCompile(pattern.chars(), pattern.length(),
ignoreCase() ? JSRegExpIgnoreCase : JSRegExpDoNotIgnoreCase,
multiline() ? JSRegExpMultiline : JSRegExpSingleLine,
parenCount, &error);
if (error) {
reportPCREError(cx, error);
return false;
}
return true;
#endif
}
inline bool
RegExpShared::compile(JSContext *cx, TokenStream *ts)
{
if (!sticky())
return code.compile(cx, *source, ts, &parenCount, getFlags());
/*
* 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));
JSLinearString *fakeySource = sb.finishString();
if (!fakeySource)
return false;
return code.compile(cx, *fakeySource, ts, &parenCount, getFlags());
}
inline RegExpRunStatus
detail::RegExpCode::execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
int *output, size_t outputCount)
{
int result;
#if ENABLE_YARR_JIT
(void) cx; /* Unused. */
if (codeBlock.isFallBack())
result = JSC::Yarr::interpret(byteCode, chars, start, length, output);
else
result = JSC::Yarr::execute(codeBlock, chars, start, length, output);
#else
result = jsRegExpExecute(cx, compiled, chars, length, start, output, outputCount);
#endif
if (result == -1)
return RegExpRunStatus_Success_NotFound;
#if !ENABLE_YARR_JIT
if (result < 0) {
reportPCREError(cx, result);
return RegExpRunStatus_Error;
}
#endif
JS_ASSERT(result >= 0);
return RegExpRunStatus_Success;
}
inline void
RegExpShared::incref(JSContext *cx)
{
++refCount;
}
inline void
RegExpShared::decref(JSContext *cx)
{
if (--refCount != 0)
return;
if (RegExpCache *cache = cx->runtime->maybeRegExpCache()) {
if (source->isAtom()) {
if (RegExpCache::Ptr p = cache->lookup(&source->asAtom())) {
if (&p->value.shared() == this)
cache->remove(p);
}
}
}
#ifdef DEBUG
this->~RegExpShared();
memset(this, 0xcd, sizeof(*this));
cx->free_(this);
#else
cx->delete_(this);
#endif
}
inline RegExpShared *
RegExpToShared(JSContext *cx, JSObject &obj)
{

View File

@ -56,30 +56,15 @@ JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB);
JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE);
JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY);
/* RegExpMatcher */
bool
RegExpMatcher::initWithTestOptimized(RegExpObject &reobj)
{
JS_ASSERT(reobj.startsWithAtomizedGreedyStar());
JS_ASSERT(!shared_);
JSAtom *source = &reobj.getSource()->asAtom();
shared_.reset(RegExpShared::createTestOptimized(cx_, source, reobj.getFlags()));
if (!shared_)
return false;
/*
* Create a dummy RegExpObject to persist this RegExpShared until the next GC.
* Note that we give the ref we have to this new object.
*/
RegExpObjectBuilder builder(cx_);
shared_->incref(cx_);
return !!builder.build(AlreadyIncRefed<RegExpShared>(shared_.get()));
}
/* RegExpObjectBuilder */
RegExpObjectBuilder::RegExpObjectBuilder(JSContext *cx, RegExpObject *reobj)
: cx(cx), reobj_(reobj)
{
if (reobj_)
reobj_->setPrivate(NULL);
}
bool
RegExpObjectBuilder::getOrCreate()
{
@ -110,30 +95,24 @@ RegExpObjectBuilder::getOrCreateClone(RegExpObject *proto)
}
RegExpObject *
RegExpObjectBuilder::build(AlreadyIncRefed<RegExpShared> shared)
{
if (!getOrCreate()) {
shared->decref(cx);
return NULL;
}
reobj_->purge(cx);
if (!reobj_->init(cx, shared->getSource(), shared->getFlags())) {
shared->decref(cx);
return NULL;
}
reobj_->setPrivate(shared.get());
return reobj_;
}
RegExpObject *
RegExpObjectBuilder::build(JSLinearString *source, RegExpFlag flags)
RegExpObjectBuilder::build(JSAtom *source, RegExpShared &shared)
{
if (!getOrCreate())
return NULL;
if (!reobj_->init(cx, source, shared.getFlags()))
return NULL;
reobj_->setPrivate(&shared);
return reobj_;
}
RegExpObject *
RegExpObjectBuilder::build(JSAtom *source, RegExpFlag flags)
{
if (!getOrCreate())
return NULL;
reobj_->purge(cx);
return reobj_->init(cx, source, flags) ? reobj_ : NULL;
}
@ -160,8 +139,7 @@ RegExpObjectBuilder::clone(RegExpObject *other, RegExpObject *proto)
if (!toShare)
return NULL;
toShare->incref(cx);
return build(AlreadyIncRefed<RegExpShared>(toShare));
return build(other->getSource(), *toShare);
}
/* MatchPairs */
@ -190,188 +168,7 @@ MatchPairs::checkAgainst(size_t inputLength)
#endif
}
RegExpRunStatus
RegExpShared::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
LifoAllocScope &allocScope, MatchPairs **output)
{
const size_t origLength = length;
size_t backingPairCount = RegExpCode::getOutputSize(pairCount());
MatchPairs *matchPairs = MatchPairs::create(allocScope.alloc(), pairCount(), backingPairCount);
if (!matchPairs)
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 start = *lastIndex;
size_t displacement = 0;
if (sticky()) {
displacement = *lastIndex;
chars += displacement;
length -= displacement;
start = 0;
}
RegExpRunStatus status = code.execute(cx, chars, length, start,
matchPairs->buffer(), backingPairCount);
switch (status) {
case RegExpRunStatus_Error:
return status;
case RegExpRunStatus_Success_NotFound:
*output = matchPairs;
return status;
default:
JS_ASSERT(status == RegExpRunStatus_Success);
}
matchPairs->displace(displacement);
matchPairs->checkAgainst(origLength);
*lastIndex = matchPairs->pair(0).limit;
*output = matchPairs;
return RegExpRunStatus_Success;
}
RegExpShared *
RegExpObject::createShared(JSContext *cx)
{
JS_ASSERT(!maybeShared());
AlreadyIncRefed<RegExpShared> shared = RegExpShared::create(cx, getSource(), getFlags(), NULL);
if (!shared)
return NULL;
setPrivate(shared.get());
return shared.get();
}
RegExpRunStatus
RegExpObject::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
LifoAllocScope &allocScope, MatchPairs **output)
{
RegExpShared *shared = getShared(cx);
if (!shared)
return RegExpRunStatus_Error;
return shared->execute(cx, chars, length, lastIndex, allocScope, output);
}
Shape *
RegExpObject::assignInitialShape(JSContext *cx)
{
JS_ASSERT(isRegExp());
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);
/* The lastIndex property alone is writable but non-configurable. */
if (!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.lastIndexAtom),
LAST_INDEX_SLOT, JSPROP_PERMANENT))
{
return NULL;
}
/* Remaining instance properties are non-writable and non-configurable. */
if (!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.sourceAtom),
SOURCE_SLOT, JSPROP_PERMANENT | JSPROP_READONLY) ||
!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.globalAtom),
GLOBAL_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY) ||
!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.ignoreCaseAtom),
IGNORE_CASE_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY) ||
!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.multilineAtom),
MULTILINE_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY))
{
return NULL;
}
return addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.stickyAtom),
STICKY_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY);
}
#if JS_HAS_XDR
#include "jsxdrapi.h"
JSBool
js_XDRRegExpObject(JSXDRState *xdr, JSObject **objp)
{
JSString *source = 0;
uint32_t flagsword = 0;
if (xdr->mode == JSXDR_ENCODE) {
JS_ASSERT(objp);
RegExpObject &reobj = (*objp)->asRegExp();
source = reobj.getSource();
flagsword = reobj.getFlags();
}
if (!JS_XDRString(xdr, &source) || !JS_XDRUint32(xdr, &flagsword))
return false;
if (xdr->mode == JSXDR_DECODE) {
JSAtom *atom = js_AtomizeString(xdr->cx, source);
if (!atom)
return false;
RegExpObject *reobj = RegExpObject::createNoStatics(xdr->cx, atom, RegExpFlag(flagsword),
NULL);
if (!reobj)
return false;
if (!reobj->clearParent(xdr->cx))
return false;
if (!reobj->clearType(xdr->cx))
return false;
*objp = reobj;
}
return true;
}
#else /* !JS_HAS_XDR */
#define js_XDRRegExpObject NULL
#endif /* !JS_HAS_XDR */
static void
regexp_finalize(JSContext *cx, JSObject *obj)
{
obj->asRegExp().finalize(cx);
}
static void
regexp_trace(JSTracer *trc, JSObject *obj)
{
if (trc->runtime->gcRunning)
obj->asRegExp().purge(trc->context);
}
Class js::RegExpClass = {
js_RegExp_str,
JSCLASS_HAS_PRIVATE |
JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) |
JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp),
JS_PropertyStub, /* addProperty */
JS_PropertyStub, /* delProperty */
JS_PropertyStub, /* getProperty */
JS_StrictPropertyStub, /* setProperty */
JS_EnumerateStub, /* enumerate */
JS_ResolveStub,
JS_ConvertStub,
regexp_finalize,
NULL, /* reserved0 */
NULL, /* checkAccess */
NULL, /* call */
NULL, /* construct */
js_XDRRegExpObject,
NULL, /* hasInstance */
regexp_trace
};
/* detail::RegExpCode */
#if ENABLE_YARR_JIT
void
@ -441,6 +238,468 @@ RegExpCode::reportPCREError(JSContext *cx, int error)
#endif /* ENABLE_YARR_JIT */
bool
RegExpCode::compile(JSContext *cx, JSLinearString &pattern, uintN *parenCount, RegExpFlag flags)
{
#if ENABLE_YARR_JIT
/* Parse the pattern. */
ErrorCode yarrError;
YarrPattern yarrPattern(pattern, bool(flags & IgnoreCaseFlag), bool(flags & MultilineFlag),
&yarrError);
if (yarrError) {
reportYarrError(cx, NULL, yarrError);
return false;
}
*parenCount = yarrPattern.m_numSubpatterns;
/*
* The YARR JIT compiler attempts to compile the parsed pattern. If
* it cannot, it informs us via |codeBlock.isFallBack()|, in which
* case we have to bytecode compile it.
*/
#ifdef JS_METHODJIT
if (isJITRuntimeEnabled(cx) && !yarrPattern.m_containsBackreferences) {
JSC::ExecutableAllocator *execAlloc = cx->runtime->getExecutableAllocator(cx);
if (!execAlloc) {
js_ReportOutOfMemory(cx);
return false;
}
JSGlobalData globalData(execAlloc);
jitCompile(yarrPattern, &globalData, codeBlock);
if (!codeBlock.isFallBack())
return true;
}
#endif
WTF::BumpPointerAllocator *bumpAlloc = cx->runtime->getBumpPointerAllocator(cx);
if (!bumpAlloc) {
js_ReportOutOfMemory(cx);
return false;
}
codeBlock.setFallBack(true);
byteCode = byteCompile(yarrPattern, bumpAlloc).get();
return true;
#else /* !defined(ENABLE_YARR_JIT) */
int error = 0;
compiled = jsRegExpCompile(pattern.chars(), pattern.length(),
ignoreCase() ? JSRegExpIgnoreCase : JSRegExpDoNotIgnoreCase,
multiline() ? JSRegExpMultiline : JSRegExpSingleLine,
parenCount, &error);
if (error) {
reportPCREError(cx, error);
return false;
}
return true;
#endif
}
RegExpRunStatus
RegExpCode::execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
int *output, size_t outputCount)
{
int result;
#if ENABLE_YARR_JIT
(void) cx; /* Unused. */
if (codeBlock.isFallBack())
result = JSC::Yarr::interpret(byteCode, chars, start, length, output);
else
result = JSC::Yarr::execute(codeBlock, chars, start, length, output);
#else
result = jsRegExpExecute(cx, compiled, chars, length, start, output, outputCount);
#endif
if (result == -1)
return RegExpRunStatus_Success_NotFound;
#if !ENABLE_YARR_JIT
if (result < 0) {
reportPCREError(cx, result);
return RegExpRunStatus_Error;
}
#endif
JS_ASSERT(result >= 0);
return RegExpRunStatus_Success;
}
/* RegExpObject */
static void
regexp_trace(JSTracer *trc, JSObject *obj)
{
if (trc->runtime->gcRunning)
obj->setPrivate(NULL);
}
Class js::RegExpClass = {
js_RegExp_str,
JSCLASS_HAS_PRIVATE |
JSCLASS_HAS_RESERVED_SLOTS(RegExpObject::RESERVED_SLOTS) |
JSCLASS_HAS_CACHED_PROTO(JSProto_RegExp),
JS_PropertyStub, /* addProperty */
JS_PropertyStub, /* delProperty */
JS_PropertyStub, /* getProperty */
JS_StrictPropertyStub, /* setProperty */
JS_EnumerateStub, /* enumerate */
JS_ResolveStub,
JS_ConvertStub,
NULL, /* finalize */
NULL, /* reserved0 */
NULL, /* checkAccess */
NULL, /* call */
NULL, /* construct */
#if JS_HAS_XDR
js_XDRRegExpObject,
#else
NULL
#endif
NULL, /* hasInstance */
regexp_trace
};
RegExpShared::RegExpShared(RegExpFlag flags)
: parenCount(0), flags(flags), activeUseCount(0)
{}
RegExpObject *
RegExpObject::create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length,
RegExpFlag flags, TokenStream *tokenStream)
{
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)
{
JSAtom *source = js_AtomizeChars(cx, chars, length);
if (!source)
return NULL;
return createNoStatics(cx, source, flags, tokenStream);
}
RegExpObject *
RegExpObject::createNoStatics(JSContext *cx, JSAtom *source, RegExpFlag flags,
TokenStream *tokenStream)
{
if (!RegExpCode::checkSyntax(cx, tokenStream, source))
return NULL;
RegExpObjectBuilder builder(cx);
return builder.build(source, flags);
}
RegExpShared *
RegExpObject::createShared(JSContext *cx)
{
JS_ASSERT(!maybeShared());
RegExpShared *shared = cx->compartment->regExps.get(cx, getSource(), getFlags());
if (!shared)
return NULL;
setPrivate(shared);
return shared;
}
Shape *
RegExpObject::assignInitialShape(JSContext *cx)
{
JS_ASSERT(isRegExp());
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);
/* The lastIndex property alone is writable but non-configurable. */
if (!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.lastIndexAtom),
LAST_INDEX_SLOT, JSPROP_PERMANENT))
{
return NULL;
}
/* Remaining instance properties are non-writable and non-configurable. */
if (!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.sourceAtom),
SOURCE_SLOT, JSPROP_PERMANENT | JSPROP_READONLY) ||
!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.globalAtom),
GLOBAL_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY) ||
!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.ignoreCaseAtom),
IGNORE_CASE_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY) ||
!addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.multilineAtom),
MULTILINE_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY))
{
return NULL;
}
return addDataProperty(cx, ATOM_TO_JSID(cx->runtime->atomState.stickyAtom),
STICKY_FLAG_SLOT, JSPROP_PERMANENT | JSPROP_READONLY);
}
inline bool
RegExpObject::init(JSContext *cx, JSAtom *source, RegExpFlag flags)
{
if (nativeEmpty()) {
if (isDelegate()) {
if (!assignInitialShape(cx))
return false;
} else {
Shape *shape = assignInitialShape(cx);
if (!shape)
return false;
EmptyShape::insertInitialShape(cx, shape, getProto());
}
JS_ASSERT(!nativeEmpty());
}
DebugOnly<JSAtomState *> atomState = &cx->runtime->atomState;
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->lastIndexAtom))->slot() == LAST_INDEX_SLOT);
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->sourceAtom))->slot() == SOURCE_SLOT);
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->globalAtom))->slot() == GLOBAL_FLAG_SLOT);
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->ignoreCaseAtom))->slot() ==
IGNORE_CASE_FLAG_SLOT);
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->multilineAtom))->slot() ==
MULTILINE_FLAG_SLOT);
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->stickyAtom))->slot() == STICKY_FLAG_SLOT);
JS_ASSERT(!maybeShared());
zeroLastIndex();
setSource(source);
setGlobal(flags & GlobalFlag);
setIgnoreCase(flags & IgnoreCaseFlag);
setMultiline(flags & MultilineFlag);
setSticky(flags & StickyFlag);
return true;
}
RegExpRunStatus
RegExpObject::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
MatchPairs **output)
{
RegExpShared *shared = getShared(cx);
if (!shared)
return RegExpRunStatus_Error;
return shared->execute(cx, chars, length, lastIndex, output);
}
JSFlatString *
RegExpObject::toString(JSContext *cx) const
{
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 */
bool
RegExpShared::compile(JSContext *cx, JSAtom *source)
{
if (!sticky())
return code.compile(cx, *source, &parenCount, getFlags());
/*
* 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 code.compile(cx, *fakeySource, &parenCount, getFlags());
}
RegExpRunStatus
RegExpShared::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
MatchPairs **output)
{
const size_t origLength = length;
size_t backingPairCount = RegExpCode::getOutputSize(pairCount());
LifoAlloc &alloc = cx->tempLifoAlloc();
MatchPairs *matchPairs = MatchPairs::create(alloc, pairCount(), backingPairCount);
if (!matchPairs)
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 start = *lastIndex;
size_t displacement = 0;
if (sticky()) {
displacement = *lastIndex;
chars += displacement;
length -= displacement;
start = 0;
}
RegExpRunStatus status = code.execute(cx, chars, length, start,
matchPairs->buffer(), backingPairCount);
switch (status) {
case RegExpRunStatus_Error:
return status;
case RegExpRunStatus_Success_NotFound:
*output = matchPairs;
return status;
default:
JS_ASSERT(status == RegExpRunStatus_Success);
}
matchPairs->displace(displacement);
matchPairs->checkAgainst(origLength);
*lastIndex = matchPairs->pair(0).limit;
*output = matchPairs;
return RegExpRunStatus_Success;
}
/* RegExpCompartment */
RegExpCompartment::RegExpCompartment(JSRuntime *rt)
: map_(rt)
{}
RegExpCompartment::~RegExpCompartment()
{
map_.empty();
}
bool
RegExpCompartment::init(JSContext *cx)
{
if (!map_.init()) {
js_ReportOutOfMemory(cx);
return false;
}
return true;
}
void
RegExpCompartment::purge()
{
for (Map::Enum e(map_); !e.empty(); e.popFront()) {
RegExpShared *shared = e.front().value;
if (shared->activeUseCount == 0) {
Foreground::delete_(shared);
e.removeFront();
}
}
}
inline RegExpShared *
RegExpCompartment::get(JSContext *cx, JSAtom *keyAtom, JSAtom *source, RegExpFlag flags, Type type)
{
DebugOnly<size_t> gcNumberBefore = cx->runtime->gcNumber;
Key key(keyAtom, flags, type);
Map::AddPtr p = map_.lookupForAdd(key);
if (p)
return p->value;
RegExpShared *shared = cx->runtime->new_<RegExpShared>(flags);
if (!shared || !shared->compile(cx, source))
goto error;
/*
* The compilation path only mallocs so cannot GC. Thus, it is safe to add
* the regexp directly.
*/
JS_ASSERT(cx->runtime->gcNumber == gcNumberBefore);
if (!map_.add(p, key, shared))
goto error;
return shared;
error:
Foreground::delete_(shared);
js_ReportOutOfMemory(cx);
return NULL;
}
RegExpShared *
RegExpCompartment::get(JSContext *cx, JSAtom *source, RegExpFlag flags)
{
return get(cx, source, source, flags, Normal);
}
RegExpShared *
RegExpCompartment::getHack(JSContext *cx, JSAtom *source, JSAtom *hackedSource, RegExpFlag flags)
{
return get(cx, source, hackedSource, flags, Hack);
}
RegExpShared *
RegExpCompartment::lookupHack(JSContext *cx, JSAtom *source, RegExpFlag flags)
{
if (Map::Ptr p = map_.lookup(Key(source, flags, Hack)))
return p->value;
return NULL;
}
RegExpShared *
RegExpCompartment::get(JSContext *cx, JSAtom *atom, JSString *opt)
{
RegExpFlag flags = RegExpFlag(0);
if (opt && !ParseRegExpFlags(cx, opt, &flags))
return NULL;
return get(cx, atom, flags);
}
/* Functions */
JSObject *
js::CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *proto)
{
JS_ASSERT(obj->isRegExp());
JS_ASSERT(proto->isRegExp());
RegExpObjectBuilder builder(cx);
return builder.clone(&obj->asRegExp(), &proto->asRegExp());
}
bool
js::ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut)
{
@ -478,101 +737,36 @@ js::ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut)
return true;
}
RegExpShared *
RegExpShared::createUncached(JSContext *cx, JSLinearString *source, RegExpFlag flags,
TokenStream *tokenStream)
{
RegExpShared *shared = cx->new_<RegExpShared>(source, flags);
if (!shared)
return NULL;
#if JS_HAS_XDR
# include "jsxdrapi.h"
if (!shared->compile(cx, tokenStream)) {
Foreground::delete_(shared);
return NULL;
JSBool
js_XDRRegExpObject(JSXDRState *xdr, JSObject **objp)
{
JSAtom *source = 0;
uint32_t flagsword = 0;
if (xdr->mode == JSXDR_ENCODE) {
JS_ASSERT(objp);
RegExpObject &reobj = (*objp)->asRegExp();
source = reobj.getSource();
flagsword = reobj.getFlags();
}
if (!js_XDRAtom(xdr, &source) || !JS_XDRUint32(xdr, &flagsword))
return false;
if (xdr->mode == JSXDR_DECODE) {
RegExpFlag flags = RegExpFlag(flagsword);
RegExpObject *reobj = RegExpObject::createNoStatics(xdr->cx, source, flags, NULL);
if (!reobj)
return false;
return shared;
}
AlreadyIncRefed<RegExpShared>
RegExpShared::createTestOptimized(JSContext *cx, JSAtom *cacheKey, RegExpFlag flags)
{
using namespace detail;
typedef AlreadyIncRefed<RegExpShared> RetType;
RetType cached;
if (!cacheLookup(cx, cacheKey, flags, RegExpCache_TestOptimized, &cached))
return RetType(NULL);
if (cached)
return cached;
/* Strip off the greedy star characters, create a new RegExpShared, and cache. */
JS_ASSERT(cacheKey->length() > JS_ARRAY_LENGTH(GreedyStarChars));
JSDependentString *stripped =
JSDependentString::new_(cx, cacheKey, cacheKey->chars() + JS_ARRAY_LENGTH(GreedyStarChars),
cacheKey->length() - JS_ARRAY_LENGTH(GreedyStarChars));
if (!stripped)
return RetType(NULL);
RegExpShared *shared = createUncached(cx, cacheKey, flags, NULL);
if (!shared)
return RetType(NULL);
if (!cacheInsert(cx, cacheKey, RegExpCache_TestOptimized, *shared)) {
shared->decref(cx);
return RetType(NULL);
if (!reobj->clearParent(xdr->cx))
return false;
if (!reobj->clearType(xdr->cx))
return false;
*objp = reobj;
}
return RetType(shared);
return true;
}
#endif /* !JS_HAS_XDR */
AlreadyIncRefed<RegExpShared>
RegExpShared::create(JSContext *cx, JSLinearString *str, JSString *opt, TokenStream *ts)
{
if (!opt)
return create(cx, str, RegExpFlag(0), ts);
RegExpFlag flags = RegExpFlag(0);
if (!ParseRegExpFlags(cx, opt, &flags))
return AlreadyIncRefed<RegExpShared>(NULL);
return create(cx, str, flags, ts);
}
JSObject * JS_FASTCALL
js_CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *proto)
{
JS_ASSERT(obj->isRegExp());
JS_ASSERT(proto->isRegExp());
RegExpObjectBuilder builder(cx);
return builder.clone(&obj->asRegExp(), &proto->asRegExp());
}
JSFlatString *
RegExpObject::toString(JSContext *cx) const
{
JSLinearString *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();
}

View File

@ -56,6 +56,26 @@
#include "yarr/pcre/pcre.h"
#endif
/*
* JavaScript Regular Expressions
*
* There are several engine concepts associated with a single logical regexp:
*
* RegExpObject - The JS-visible object whose .[[Class]] equals "RegExp"
*
* RegExpShared - The compiled representation of the regexp.
*
* RegExpCode - The low-level implementation jit details.
*
* RegExpCompartment - Owns all RegExpShared instances in a compartment.
*
* To save memory, a RegExpShared is not created for a RegExpObject until it is
* needed for execution. When a RegExpShared needs to be created, it is looked
* up in a per-compartment table to allow reuse between objects. Lastly, on
* GC, every RegExpShared (that is not active on the callstack) is discarded.
* Because of the last point, any code using a RegExpShared (viz., by executing
* a regexp) must indicate the RegExpShared is active via RegExpShared::Guard.
*/
namespace js {
enum RegExpRunStatus
@ -84,19 +104,16 @@ class RegExpObject : public JSObject
* so this function is really meant for object creation during code
* execution, as opposed to during something like XDR.
*/
static inline RegExpObject *
create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length, RegExpFlag flags,
TokenStream *tokenStream);
static RegExpObject *
create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length,
RegExpFlag flags, TokenStream *ts);
static inline RegExpObject *
static RegExpObject *
createNoStatics(JSContext *cx, const jschar *chars, size_t length, RegExpFlag flags,
TokenStream *tokenStream);
TokenStream *ts);
static inline RegExpObject *
createNoStatics(JSContext *cx, JSAtom *atom, RegExpFlag flags, TokenStream *tokenStream);
/* Note: fallible. */
JSFlatString *toString(JSContext *cx) const;
static RegExpObject *
createNoStatics(JSContext *cx, JSAtom *atom, RegExpFlag flags, TokenStream *ts);
/*
* Run the regular expression over the input text.
@ -109,8 +126,9 @@ class RegExpObject : public JSObject
* N.B. it's the responsibility of the caller to hook the |output|
* into the |RegExpStatics| appropriately, if necessary.
*/
RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
LifoAllocScope &allocScope, MatchPairs **output);
RegExpRunStatus
execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
MatchPairs **output);
/* Accessors. */
@ -121,10 +139,12 @@ class RegExpObject : public JSObject
inline void setLastIndex(double d);
inline void zeroLastIndex();
JSLinearString *getSource() const {
return &getSlot(SOURCE_SLOT).toString()->asLinear();
JSFlatString *toString(JSContext *cx) const;
JSAtom *getSource() const {
return &getSlot(SOURCE_SLOT).toString()->asAtom();
}
inline void setSource(JSLinearString *source);
inline void setSource(JSAtom *source);
RegExpFlag getFlags() const {
uintN flags = 0;
@ -135,12 +155,6 @@ class RegExpObject : public JSObject
return RegExpFlag(flags);
}
inline bool startsWithAtomizedGreedyStar() const;
/* JIT only. */
inline size_t *addressOfPrivateRefCount() const;
/* Flags. */
inline void setIgnoreCase(bool enabled);
@ -152,40 +166,12 @@ class RegExpObject : public JSObject
bool multiline() const { return getSlot(MULTILINE_FLAG_SLOT).toBoolean(); }
bool sticky() const { return getSlot(STICKY_FLAG_SLOT).toBoolean(); }
inline void finalize(JSContext *cx);
/* Clear out lazy |RegExpShared|. */
inline void purge(JSContext *x);
RegExpShared &shared() const {
JS_ASSERT(JSObject::getPrivate() != NULL);
return *static_cast<RegExpShared *>(JSObject::getPrivate());
}
RegExpShared *maybeShared() {
return static_cast<RegExpShared *>(JSObject::getPrivate());
}
RegExpShared *getShared(JSContext *cx) {
if (RegExpShared *shared = maybeShared())
return shared;
return createShared(cx);
}
inline RegExpShared &shared() const;
inline RegExpShared *maybeShared();
inline RegExpShared *getShared(JSContext *cx);
private:
friend class RegExpObjectBuilder;
friend class RegExpMatcher;
inline bool init(JSContext *cx, JSLinearString *source, RegExpFlag flags);
/*
* Precondition: the syntax for |source| has already been validated.
* Side effect: sets the private field.
*/
RegExpShared *createShared(JSContext *cx);
friend bool ResetRegExpObject(JSContext *, RegExpObject *, JSLinearString *, RegExpFlag);
friend bool ResetRegExpObject(JSContext *, RegExpObject *, AlreadyIncRefed<RegExpShared>);
/*
* Compute the initial shape to associate with fresh RegExp objects,
@ -194,11 +180,18 @@ class RegExpObject : public JSObject
*/
Shape *assignInitialShape(JSContext *cx);
inline bool init(JSContext *cx, JSAtom *source, RegExpFlag flags);
/*
* Precondition: the syntax for |source| has already been validated.
* Side effect: sets the private field.
*/
RegExpShared *createShared(JSContext *cx);
RegExpObject() MOZ_DELETE;
RegExpObject &operator=(const RegExpObject &reo) MOZ_DELETE;
}; /* class RegExpObject */
};
/* Either builds a new RegExpObject or re-initializes an existing one. */
class RegExpObjectBuilder
{
JSContext *cx;
@ -207,27 +200,23 @@ class RegExpObjectBuilder
bool getOrCreate();
bool getOrCreateClone(RegExpObject *proto);
friend class RegExpMatcher;
public:
RegExpObjectBuilder(JSContext *cx, RegExpObject *reobj = NULL)
: cx(cx), reobj_(reobj)
{ }
RegExpObjectBuilder(JSContext *cx, RegExpObject *reobj = NULL);
RegExpObject *reobj() { return reobj_; }
RegExpObject *build(JSLinearString *str, RegExpFlag flags);
RegExpObject *build(AlreadyIncRefed<RegExpShared> shared);
RegExpObject *build(JSAtom *source, RegExpFlag flags);
RegExpObject *build(JSAtom *source, RegExpShared &shared);
/* Perform a VM-internal clone. */
RegExpObject *clone(RegExpObject *other, RegExpObject *proto);
};
JSObject *
CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *proto);
namespace detail {
static const jschar GreedyStarChars[] = {'.', '*'};
/* Abstracts away the gross |RegExpShared| backend details. */
class RegExpCode
{
#if ENABLE_YARR_JIT
@ -294,168 +283,138 @@ class RegExpCode
#endif
}
inline bool compile(JSContext *cx, JSLinearString &pattern, TokenStream *ts, uintN *parenCount,
RegExpFlag flags);
bool compile(JSContext *cx, JSLinearString &pattern, uintN *parenCount, RegExpFlag flags);
inline RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
int *output, size_t outputCount);
RegExpRunStatus
execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
int *output, size_t outputCount);
};
enum RegExpCacheKind
{
RegExpCache_TestOptimized,
RegExpCache_ExecCapable
};
} /* namespace detail */
class RegExpCacheValue
{
union {
RegExpShared *shared_;
uintptr_t bits;
};
public:
RegExpCacheValue() : shared_(NULL) {}
RegExpCacheValue(RegExpShared &shared, RegExpCacheKind kind) {
reset(shared, kind);
}
RegExpCacheKind kind() const {
return (bits & 0x1)
? RegExpCache_TestOptimized
: RegExpCache_ExecCapable;
}
RegExpShared &shared() {
return *reinterpret_cast<RegExpShared *>(bits & ~uintptr_t(1));
}
void reset(RegExpShared &shared, RegExpCacheKind kind) {
shared_ = &shared;
if (kind == RegExpCache_TestOptimized)
bits |= 0x1;
JS_ASSERT(this->kind() == kind);
}
};
} /* namespace detail */
/*
* The "meat" of the builtin regular expression objects: it contains the
* mini-program that represents the source of the regular expression.
* Excepting refcounts, this is an immutable datastructure after
* compilation.
*
* Note: refCount cannot overflow because that would require more
* referring regexp objects than there is space for in addressable
* memory.
*/
/* The compiled representation of a regexp. */
class RegExpShared
{
typedef detail::RegExpCode RegExpCode;
typedef detail::RegExpCacheKind RegExpCacheKind;
typedef detail::RegExpCacheValue RegExpCacheValue;
friend class RegExpCompartment;
RegExpCode code;
JSLinearString *source;
size_t refCount;
uintN parenCount;
RegExpFlag flags;
detail::RegExpCode code;
uintN parenCount;
RegExpFlag flags;
size_t activeUseCount;
private:
RegExpShared(JSLinearString *source, RegExpFlag flags)
: source(source), refCount(1), parenCount(0), flags(flags)
{ }
bool compile(JSContext *cx, JSAtom *source);
RegExpShared(RegExpFlag flags);
JS_DECLARE_ALLOCATION_FRIENDS_FOR_PRIVATE_CONSTRUCTOR;
bool compile(JSContext *cx, TokenStream *ts);
static inline void checkMatchPairs(JSString *input, int *buf, size_t matchItemCount);
static RegExpShared *
createUncached(JSContext *cx, JSLinearString *source, RegExpFlag flags,
TokenStream *tokenStream);
static bool cacheLookup(JSContext *cx, JSAtom *atom, RegExpFlag flags,
RegExpCacheKind kind, AlreadyIncRefed<RegExpShared> *result);
static bool cacheInsert(JSContext *cx, JSAtom *atom,
RegExpCacheKind kind, RegExpShared &shared);
public:
static AlreadyIncRefed<RegExpShared>
create(JSContext *cx, JSLinearString *source, RegExpFlag flags, TokenStream *ts);
/*
* Extend the lifetime of a given RegExpShared to at least the lifetime of
* the Guard object. See Regular Expression comment at the top.
*/
class Guard {
RegExpShared *re_;
Guard(const Guard &) MOZ_DELETE;
void operator=(const Guard &) MOZ_DELETE;
public:
Guard() : re_(NULL) {}
Guard(RegExpShared &re) : re_(&re) {
re_->activeUseCount++;
}
void init(RegExpShared &re) {
JS_ASSERT(!re_);
re_ = &re;
re_->activeUseCount++;
}
~Guard() {
if (re_) {
JS_ASSERT(re_->activeUseCount > 0);
re_->activeUseCount--;
}
}
bool initialized() const { return !!re_; }
RegExpShared *operator->() { JS_ASSERT(initialized()); return re_; }
RegExpShared &operator*() { JS_ASSERT(initialized()); return *re_; }
};
static AlreadyIncRefed<RegExpShared>
create(JSContext *cx, JSLinearString *source, JSString *flags, TokenStream *ts);
/* Primary interface: run this regular expression on the given string. */
static AlreadyIncRefed<RegExpShared>
createTestOptimized(JSContext *cx, JSAtom *originalSource, RegExpFlag flags);
RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
LifoAllocScope &allocScope, MatchPairs **output);
/* Mutators */
void incref(JSContext *cx);
void decref(JSContext *cx);
/* For JIT access. */
size_t *addressOfRefCount() { return &refCount; }
RegExpRunStatus
execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
MatchPairs **output);
/* Accessors */
JSLinearString *getSource() const { return source; }
size_t getParenCount() const { return parenCount; }
/* Accounts for the "0" (whole match) pair. */
size_t pairCount() const { return parenCount + 1; }
RegExpFlag getFlags() const { return flags; }
bool ignoreCase() const { return flags & IgnoreCaseFlag; }
bool global() const { return flags & GlobalFlag; }
bool multiline() const { return flags & MultilineFlag; }
bool sticky() const { return flags & StickyFlag; }
bool ignoreCase() const { return flags & IgnoreCaseFlag; }
bool global() const { return flags & GlobalFlag; }
bool multiline() const { return flags & MultilineFlag; }
bool sticky() const { return flags & StickyFlag; }
};
/*
* Wraps the RegExpObject's internals in a recount-safe interface for
* use in RegExp execution. This is used in situations where we'd like
* to avoid creating a full-fledged RegExpObject. This interface is
* provided in lieu of exposing the RegExpShared directly.
*
* Note: this exposes precisely the execute interface of a RegExpObject.
*/
class RegExpMatcher
class RegExpCompartment
{
JSContext *cx_;
AutoRefCount<RegExpShared> shared_;
enum Type { Normal = 0x0, Hack = 0x1 };
struct Key {
JSAtom *atom;
uint16_t flag;
uint16_t type;
Key() {}
Key(JSAtom *atom, RegExpFlag flag, Type type)
: atom(atom), flag(flag), type(type) {}
typedef Key Lookup;
static HashNumber hash(const Lookup &l) {
return DefaultHasher<JSAtom *>::hash(l.atom) ^ (l.flag << 1) ^ l.type;
}
static bool match(Key l, Key r) {
return l.atom == r.atom && l.flag == r.flag && l.type == r.type;
}
};
typedef HashMap<Key, RegExpShared *, Key, RuntimeAllocPolicy> Map;
Map map_;
RegExpShared *get(JSContext *cx, JSAtom *key, JSAtom *source, RegExpFlag flags, Type type);
public:
explicit RegExpMatcher(JSContext *cx) : cx_(cx), shared_(cx) {
JS_ASSERT(!initialized());
}
RegExpCompartment(JSRuntime *rt);
~RegExpCompartment();
bool initialized() const {
return !shared_.null();
}
bool global() const {
return shared_->global();
}
bool sticky() const {
return shared_->sticky();
}
bool init(JSContext *cx);
void purge();
inline void init(NeedsIncRef<RegExpShared> shared);
inline bool init(JSLinearString *patstr, JSString *opt);
bool initWithTestOptimized(RegExpObject &reobj);
/* Return a regexp corresponding to the given (source, flags) pair. */
RegExpShared *get(JSContext *cx, JSAtom *source, RegExpFlag flags);
RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
LifoAllocScope &allocScope, MatchPairs **output) const {
JS_ASSERT(initialized());
return shared_->execute(cx, chars, length, lastIndex, allocScope, output);
}
/* Like 'get', but compile 'maybeOpt' (if non-null). */
RegExpShared *get(JSContext *cx, JSAtom *source, JSString *maybeOpt);
/*
* A 'hacked' RegExpShared is one where the input 'source' doesn't match
* what is actually compiled in the regexp. To compile a hacked regexp,
* getHack may be called providing both the original 'source' and the
* 'hackedSource' which should actually be compiled. For a given 'source'
* there may only ever be one corresponding 'hackedSource'. Thus, we assume
* there is some single pure function mapping 'source' to 'hackedSource'
* that is always respected in calls to getHack. Note that this restriction
* only applies to 'getHack': a single 'source' value may be passed to both
* 'get' and 'getHack'.
*/
RegExpShared *getHack(JSContext *cx, JSAtom *source, JSAtom *hackedSource, RegExpFlag flags);
/*
* To avoid atomizing 'hackedSource', callers may call 'lookupHack',
* passing only the original 'source'. Due to the abovementioned unique
* mapping property, 'hackedSource' is unambiguous.
*/
RegExpShared *lookupHack(JSContext *cx, JSAtom *source, RegExpFlag flags);
};
/*
@ -467,23 +426,19 @@ class RegExpMatcher
bool
ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut);
inline bool
IsRegExpMetaChar(jschar c);
inline bool
CheckRegExpSyntax(JSContext *cx, JSLinearString *str)
{
return detail::RegExpCode::checkSyntax(cx, NULL, str);
}
/*
* Assuming ObjectClassIs(obj, ESClass_RegExp), return obj's RegExpShared.
*
* Beware: this RegExpShared can be owned by a compartment other than
* cx->compartment. Normal RegExpShared::Guard (which is necessary anyways)
* will protect the object but it is important not to assign the return value
* to be the private of any RegExpObject.
*/
inline RegExpShared *
RegExpToShared(JSContext *cx, JSObject &obj);
} /* namespace js */
extern JS_FRIEND_API(JSObject *) JS_FASTCALL
js_CloneRegExpObject(JSContext *cx, JSObject *obj, JSObject *proto);
JSBool
js_XDRRegExpObject(JSXDRState *xdr, JSObject **objp);