Bug 673188: Compile regexps lazily. (r=mrbkap)

This commit is contained in:
Chris Leary 2011-10-07 03:04:00 -07:00
Родитель 07d0d35b47
Коммит 838539811c
12 изменённых файлов: 670 добавлений и 499 удалений

Просмотреть файл

@ -48,56 +48,170 @@
using namespace js;
using namespace js::types;
/*
* Return:
* - The original if no escaping need be performed.
* - A new string if escaping need be performed.
* - NULL on error.
*/
static JSString *
EscapeNakedForwardSlashes(JSContext *cx, JSString *unescaped)
class RegExpMatchBuilder
{
JSContext * const cx;
JSObject * const array;
bool setProperty(JSAtom *name, Value v) {
return !!js_DefineProperty(cx, array, ATOM_TO_JSID(name), &v,
JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE);
}
public:
RegExpMatchBuilder(JSContext *cx, JSObject *array) : cx(cx), array(array) {}
bool append(uint32 index, Value v) {
JS_ASSERT(!array->getOps()->getElement);
return !!js_DefineElement(cx, array, index, &v, JS_PropertyStub, JS_StrictPropertyStub,
JSPROP_ENUMERATE);
}
bool setIndex(int index) {
return setProperty(cx->runtime->atomState.indexAtom, Int32Value(index));
}
bool setInput(JSString *str) {
JS_ASSERT(str);
return setProperty(cx->runtime->atomState.inputAtom, StringValue(str));
}
};
static bool
CreateRegExpMatchResult(JSContext *cx, JSString *input, const jschar *chars, size_t length,
MatchPairs *matchPairs, Value *rval)
{
/*
* Create the (slow) result array for a match.
*
* Array contents:
* 0: matched string
* 1..pairCount-1: paren matches
* input: input string
* index: start index for the match
*/
JSObject *array = NewSlowEmptyArray(cx);
if (!array)
return false;
if (!input) {
input = js_NewStringCopyN(cx, chars, length);
if (!input)
return false;
}
RegExpMatchBuilder builder(cx, array);
for (size_t i = 0; i < matchPairs->pairCount(); ++i) {
MatchPair pair = matchPairs->pair(i);
JSString *captured;
if (pair.isUndefined()) {
JS_ASSERT(i != 0); /* Since we had a match, first pair must be present. */
if (!builder.append(i, UndefinedValue()))
return false;
} else {
captured = js_NewDependentString(cx, input, pair.start, pair.length());
if (!captured || !builder.append(i, StringValue(captured)))
return false;
}
}
if (!builder.setIndex(matchPairs->pair(0).start) || !builder.setInput(input))
return false;
*rval = ObjectValue(*array);
return true;
}
template <class T>
bool
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);
switch (status) {
case RegExpRunStatus_Error:
return false;
case RegExpRunStatus_Success_NotFound:
*rval = NullValue();
return true;
default:
JS_ASSERT(status == RegExpRunStatus_Success);
JS_ASSERT(matchPairs);
}
if (res)
res->updateFromMatchPairs(cx, input, matchPairs);
*lastIndex = matchPairs->pair(0).limit;
if (type == RegExpTest) {
*rval = BooleanValue(true);
return true;
}
return CreateRegExpMatchResult(cx, input, chars, length, matchPairs, rval);
}
bool
js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpPrivate *rep, JSLinearString *input,
const jschar *chars, size_t length,
size_t *lastIndex, RegExpExecType type, Value *rval)
{
return ExecuteRegExpImpl(cx, res, rep, input, chars, length, lastIndex, type, rval);
}
bool
js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject *reobj, JSLinearString *input,
const jschar *chars, size_t length,
size_t *lastIndex, RegExpExecType type, Value *rval)
{
return ExecuteRegExpImpl(cx, res, reobj, input, chars, length, lastIndex, type, rval);
}
/* Note: returns the original if no escaping need be performed. */
static JSLinearString *
EscapeNakedForwardSlashes(JSContext *cx, JSLinearString *unescaped)
{
size_t oldLen = unescaped->length();
const jschar *oldChars = unescaped->getChars(cx);
if (!oldChars)
return NULL;
const jschar *oldChars = unescaped->chars();
JS::Anchor<JSString *> anchor(unescaped);
js::Vector<jschar, 128> newChars(cx);
/* We may never need to use |sb|. Start using it lazily. */
StringBuffer sb(cx);
for (const jschar *it = oldChars; it < oldChars + oldLen; ++it) {
if (*it == '/' && (it == oldChars || it[-1] != '\\')) {
if (!newChars.length()) {
if (!newChars.reserve(oldLen + 1))
/* There's a forward slash that needs escaping. */
if (sb.empty()) {
/* This is the first one we've seen, copy everything up to this point. */
if (!sb.reserve(oldLen + 1))
return NULL;
newChars.infallibleAppend(oldChars, size_t(it - oldChars));
sb.infallibleAppend(oldChars, size_t(it - oldChars));
}
if (!newChars.append('\\'))
if (!sb.append('\\'))
return NULL;
}
if (!newChars.empty() && !newChars.append(*it))
if (!sb.empty() && !sb.append(*it))
return NULL;
}
if (newChars.empty())
return unescaped;
size_t len = newChars.length();
if (!newChars.append('\0'))
return NULL;
jschar *chars = newChars.extractRawBuffer();
JSString *escaped = js_NewString(cx, chars, len);
if (!escaped)
cx->free_(chars);
return escaped;
return sb.empty() ? unescaped : sb.finishString();
}
static bool
ResetRegExpObjectWithStatics(JSContext *cx, RegExpObject *reobj,
JSString *str, RegExpFlag flags = RegExpFlag(0))
JSLinearString *str, RegExpFlag flags = RegExpFlag(0))
{
flags = RegExpFlag(flags | cx->regExpStatics()->getFlags());
return ResetRegExpObject(cx, reobj, str, flags);
return reobj->reset(cx, str, flags);
}
/*
@ -125,7 +239,7 @@ CompileRegExpObject(JSContext *cx, RegExpObject *obj, uintN argc, Value *argv, V
if (ValueIsRegExp(sourceValue)) {
/*
* If we get passed in a |RegExpObject| source we return a new
* object with the same |RegExpPrivate|.
* object with the same source/flags.
*
* Note: the regexp static flags are not taken into consideration here.
*/
@ -135,23 +249,21 @@ CompileRegExpObject(JSContext *cx, RegExpObject *obj, uintN argc, Value *argv, V
return false;
}
RegExpPrivate *rep = sourceObj.asRegExp()->getPrivate();
if (!rep)
return false;
rep->incref(cx);
if (!ResetRegExpObject(cx, obj, AlreadyIncRefed<RegExpPrivate>(rep)))
if (!obj->reset(cx, sourceObj.asRegExp()))
return false;
*rval = ObjectValue(*obj);
return true;
}
JSString *sourceStr;
JSLinearString *sourceStr;
if (sourceValue.isUndefined()) {
sourceStr = cx->runtime->emptyString;
} else {
/* Coerce to string and compile. */
sourceStr = js_ValueToString(cx, sourceValue);
JSString *str = js_ValueToString(cx, sourceValue);
if (!str)
return false;
sourceStr = str->ensureLinear(cx);
if (!sourceStr)
return false;
}
@ -166,10 +278,13 @@ CompileRegExpObject(JSContext *cx, RegExpObject *obj, uintN argc, Value *argv, V
return false;
}
JSString *escapedSourceStr = EscapeNakedForwardSlashes(cx, sourceStr);
JSLinearString *escapedSourceStr = EscapeNakedForwardSlashes(cx, sourceStr);
if (!escapedSourceStr)
return false;
if (!RegExpPrivateCode::checkSyntax(cx, NULL, escapedSourceStr))
return false;
if (!ResetRegExpObjectWithStatics(cx, obj, escapedSourceStr, flags))
return false;
*rval = ObjectValue(*obj);
@ -187,9 +302,7 @@ regexp_compile(JSContext *cx, uintN argc, Value *vp)
return ok;
RegExpObject *reobj = obj->asRegExp();
ok = CompileRegExpObject(cx, reobj, args.length(), args.array(), &args.rval());
JS_ASSERT_IF(ok, reobj->getPrivate());
return ok;
return CompileRegExpObject(cx, reobj, args.length(), args.array(), &args.rval());
}
static JSBool
@ -213,16 +326,10 @@ regexp_construct(JSContext *cx, uintN argc, Value *vp)
if (!obj)
return false;
PreInitRegExpObject pireo(obj);
RegExpObject *reobj = pireo.get();
if (!CompileRegExpObject(cx, reobj, argc, argv, &JS_RVAL(cx, vp))) {
pireo.fail();
if (!CompileRegExpObject(cx, obj->asRegExp(), argc, argv, &JS_RVAL(cx, vp)))
return false;
}
pireo.succeed();
*vp = ObjectValue(*reobj);
*vp = ObjectValue(*obj);
return true;
}
@ -356,30 +463,9 @@ js_InitRegExpClass(JSContext *cx, JSObject *obj)
if (!proto)
return NULL;
{
AlreadyIncRefed<RegExpPrivate> rep =
RegExpPrivate::create(cx, cx->runtime->emptyString, RegExpFlag(0), NULL);
if (!rep)
return NULL;
/*
* Associate the empty regular expression with |RegExp.prototype|, and define
* the initial non-method properties of any regular expression instance.
* These must be added before methods to preserve slot layout.
*/
#ifdef DEBUG
assertSameCompartment(cx, proto, rep->compartment);
#endif
PreInitRegExpObject pireo(proto);
RegExpObject *reproto = pireo.get();
if (!ResetRegExpObject(cx, reproto, rep)) {
pireo.fail();
return NULL;
}
pireo.succeed();
}
RegExpObject *reproto = proto->asRegExp();
if (!reproto->reset(cx, cx->runtime->emptyString, RegExpFlag(0)))
return NULL;
if (!DefinePropertiesAndBrand(cx, proto, NULL, regexp_methods))
return NULL;
@ -419,7 +505,7 @@ js_InitRegExpClass(JSContext *cx, JSObject *obj)
* RegExp.prototype.test doesn't need to create a results array, and we use
* |execType| to perform this optimization.
*/
static JSBool
static bool
ExecuteRegExp(JSContext *cx, Native native, uintN argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
@ -431,23 +517,25 @@ ExecuteRegExp(JSContext *cx, Native native, uintN argc, Value *vp)
return ok;
RegExpObject *reobj = obj->asRegExp();
RegExpPrivate *re = reobj->getPrivate();
if (!re)
RegExpPrivate *rep = reobj->getOrCreatePrivate(cx);
if (!rep)
return true;
/*
* Code execution under this call could swap out the guts of |reobj|, so we
* have to take a defensive refcount here.
*/
AutoRefCount<RegExpPrivate> arc(cx, NeedsIncRef<RegExpPrivate>(re));
AutoRefCount<RegExpPrivate> arc(cx, NeedsIncRef<RegExpPrivate>(rep));
RegExpStatics *res = cx->regExpStatics();
/* Step 2. */
JSString *input = js_ValueToString(cx, args.length() > 0 ? args[0] : UndefinedValue());
JSString *input = js_ValueToString(cx, args.length() > 0 ? args[0] : UndefinedValue());
if (!input)
return false;
/* Step 3. */
JSLinearString *linearInput = input->ensureLinear(cx);
const jschar *chars = linearInput->chars();
size_t length = input->length();
/* Step 4. */
@ -459,7 +547,7 @@ ExecuteRegExp(JSContext *cx, Native native, uintN argc, Value *vp)
return false;
/* Steps 6-7 (with sticky extension). */
if (!re->global() && !re->sticky())
if (!rep->global() && !rep->sticky())
i = 0;
/* Step 9a. */
@ -470,12 +558,15 @@ ExecuteRegExp(JSContext *cx, Native native, uintN argc, Value *vp)
}
/* Steps 8-21. */
RegExpExecType execType = native == regexp_test ? RegExpTest : RegExpExec;
size_t lastIndexInt(i);
if (!re->execute(cx, res, input, &lastIndexInt, native == regexp_test, &args.rval()))
if (!ExecuteRegExp(cx, res, rep, linearInput, chars, length, &lastIndexInt, execType,
&args.rval())) {
return false;
}
/* Step 11 (with sticky extension). */
if (re->global() || (!args.rval().isNull() && re->sticky())) {
if (rep->global() || (!args.rval().isNull() && rep->sticky())) {
if (args.rval().isNull())
reobj->zeroLastIndex();
else

Просмотреть файл

@ -53,6 +53,21 @@ js_InitRegExpClass(JSContext *cx, JSObject *obj);
namespace js {
/*
* |res| may be null if the |RegExpStatics| are not to be updated.
* |input| may be null if there is no |JSString| corresponding to
* |chars| and |length|.
*/
bool
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, RegExpPrivate *rep, JSLinearString *input,
const jschar *chars, size_t length,
size_t *lastIndex, RegExpExecType type, Value *rval);
extern JSBool
regexp_exec(JSContext *cx, uintN argc, Value *vp);

Просмотреть файл

@ -324,6 +324,10 @@ class LifoAllocScope {
lifoAlloc->release(mark);
}
LifoAlloc &alloc() {
return *lifoAlloc;
}
void releaseEarly() {
JS_ASSERT(shouldRelease);
lifoAlloc->release(mark);

Просмотреть файл

@ -5961,16 +5961,9 @@ JS_ExecuteRegExp(JSContext *cx, JSObject *obj, JSObject *reobj, jschar *chars, s
{
CHECK_REQUEST(cx);
RegExpPrivate *rep = reobj->asRegExp()->getPrivate();
if (!rep)
return false;
JSString *str = js_NewStringCopyN(cx, chars, length);
if (!str)
return false;
RegExpStatics *res = obj->asGlobal()->getRegExpStatics();
return rep->execute(cx, res, str, indexp, test, rval);
return ExecuteRegExp(cx, res, reobj->asRegExp(), NULL, chars, length,
indexp, test ? RegExpTest : RegExpExec, rval);
}
JS_PUBLIC_API(JSObject *)
@ -5998,15 +5991,8 @@ JS_ExecuteRegExpNoStatics(JSContext *cx, JSObject *obj, jschar *chars, size_t le
{
CHECK_REQUEST(cx);
RegExpPrivate *rep = obj->asRegExp()->getPrivate();
if (!rep)
return false;
JSString *str = js_NewStringCopyN(cx, chars, length);
if (!str)
return false;
return rep->executeNoStatics(cx, str, indexp, test, rval);
return ExecuteRegExp(cx, NULL, obj->asRegExp(), NULL, chars, length, indexp,
test ? RegExpTest : RegExpExec, rval);
}
JS_PUBLIC_API(JSBool)

Просмотреть файл

@ -128,8 +128,10 @@ namespace js {
struct ArgumentsData;
struct Class;
class RegExpObject;
class RegExpPrivate;
class RegExpStatics;
class MatchPairs;
enum RegExpFlag
{
@ -139,6 +141,12 @@ enum RegExpFlag
StickyFlag = JS_BIT(3)
};
enum RegExpExecType
{
RegExpExec,
RegExpTest
};
class AutoStringRooter;
class ExecuteArgsGuard;
class InvokeFrameGuard;

Просмотреть файл

@ -72,6 +72,7 @@
#include "jsstr.h"
#include "jsversion.h"
#include "builtin/RegExp.h"
#include "vm/GlobalObject.h"
#include "vm/RegExpObject.h"
@ -517,7 +518,7 @@ js_str_toString(JSContext *cx, uintN argc, Value *vp)
/*
* Java-like string native methods.
*/
JS_ALWAYS_INLINE bool
ValueToIntegerRange(JSContext *cx, const Value &v, int32 *out)
{
@ -531,7 +532,7 @@ ValueToIntegerRange(JSContext *cx, const Value &v, int32 *out)
*out = INT32_MAX;
else if (d < INT32_MIN)
*out = INT32_MIN;
else
else
*out = int32(d);
}
@ -1253,7 +1254,7 @@ class FlatMatch
public:
FlatMatch() : patstr(NULL) {} /* Old GCC wants this initialization. */
JSString *pattern() const { return patstr; }
JSLinearString *pattern() const { return patstr; }
size_t patternLength() const { return patlen; }
/*
@ -1263,33 +1264,39 @@ class FlatMatch
int32 match() const { return match_; }
};
/* A regexp and optional associated object. */
class RegExpPair
{
AutoRefCount<RegExpPrivate> rep_;
JSObject *reobj_;
RegExpObject *reobj_;
explicit RegExpPair(RegExpPair &);
void operator=(const RegExpPair &);
public:
explicit RegExpPair(JSContext *cx) : rep_(cx) {}
void reset(JSObject &obj) {
reobj_ = &obj;
RegExpPrivate *rep = reobj_->asRegExp()->getPrivate();
JS_ASSERT(rep);
bool resetWithObject(JSContext *cx, RegExpObject *reobj) {
reobj_ = reobj;
RegExpPrivate *rep = reobj_->asRegExp()->getOrCreatePrivate(cx);
if (!rep)
return false;
rep_.reset(NeedsIncRef<RegExpPrivate>(rep));
return true;
}
void reset(AlreadyIncRefed<RegExpPrivate> rep) {
void resetWithPrivate(AlreadyIncRefed<RegExpPrivate> rep) {
reobj_ = NULL;
rep_.reset(rep);
}
/* Note: May be null. */
JSObject *reobj() const { return reobj_; }
bool hasRegExp() const { return !rep_.null(); }
RegExpPrivate &re() const { JS_ASSERT(hasRegExp()); return *rep_; }
bool null() const { return rep_.null(); }
RegExpObject *reobj() const { return reobj_; }
RegExpPrivate *getPrivate() const {
JS_ASSERT(!null());
return rep_.get();
}
};
/*
@ -1314,7 +1321,7 @@ class RegExpGuard
*/
static const size_t MAX_FLAT_PAT_LEN = 256;
static JSString *flattenPattern(JSContext *cx, JSLinearString *patstr) {
static JSLinearString *flattenPattern(JSContext *cx, JSLinearString *patstr) {
StringBuffer sb(cx);
if (!sb.reserve(patstr->length()))
return NULL;
@ -1343,13 +1350,13 @@ class RegExpGuard
init(uintN argc, Value *vp, bool convertVoid = false)
{
if (argc != 0 && ValueIsRegExp(vp[2])) {
rep.reset(vp[2].toObject());
rep.resetWithObject(cx, vp[2].toObject().asRegExp());
} else {
if (convertVoid && (argc == 0 || vp[2].isUndefined())) {
fm.patstr = cx->runtime->emptyString;
return true;
}
fm.patstr = ArgToRootedString(cx, argc, vp, 0);
if (!fm.patstr)
return false;
@ -1370,7 +1377,7 @@ class RegExpGuard
tryFlatMatch(JSContext *cx, JSString *textstr, uintN optarg, uintN argc,
bool checkMetaChars = true)
{
if (rep.hasRegExp())
if (!rep.null())
return NULL;
fm.pat = fm.patstr->chars();
@ -1403,7 +1410,7 @@ class RegExpGuard
const RegExpPair *
normalizeRegExp(bool flat, uintN optarg, uintN argc, Value *vp)
{
if (rep.hasRegExp())
if (!rep.null())
return &rep;
/* Build RegExp from pattern string. */
@ -1416,7 +1423,7 @@ class RegExpGuard
opt = NULL;
}
JSString *patstr;
JSLinearString *patstr;
if (flat) {
patstr = flattenPattern(cx, fm.patstr);
if (!patstr)
@ -1426,23 +1433,23 @@ class RegExpGuard
}
JS_ASSERT(patstr);
AlreadyIncRefed<RegExpPrivate> re = RegExpPrivate::createFlagged(cx, patstr, opt, NULL);
AlreadyIncRefed<RegExpPrivate> re = RegExpPrivate::create(cx, patstr, opt, NULL);
if (!re)
return NULL;
rep.reset(re);
rep.resetWithPrivate(re);
return &rep;
}
#if DEBUG
bool hasRegExpPair() const { return rep.hasRegExp(); }
bool hasRegExpPair() const { return !rep.null(); }
#endif
};
/* js_ExecuteRegExp indicates success in two ways, based on the 'test' flag. */
/* ExecuteRegExp indicates success in two ways, based on the 'test' flag. */
static JS_ALWAYS_INLINE bool
Matched(bool test, const Value &v)
Matched(RegExpExecType type, const Value &v)
{
return test ? v.isTrue() : !v.isNull();
return type == RegExpTest ? v.isTrue() : !v.isNull();
}
typedef bool (*DoMatchCallback)(JSContext *cx, RegExpStatics *res, size_t count, void *data);
@ -1463,19 +1470,26 @@ enum MatchControlFlags {
/* Factor out looping and matching logic. */
static bool
DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, const RegExpPair &rep,
DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, const RegExpPair &regExpPair,
DoMatchCallback callback, void *data, MatchControlFlags flags, Value *rval)
{
RegExpPrivate &re = rep.re();
if (re.global()) {
RegExpPrivate *rep = regExpPair.getPrivate();
JSLinearString *linearStr = str->ensureLinear(cx);
if (!linearStr)
return false;
const jschar *chars = linearStr->chars();
size_t length = linearStr->length();
if (rep->global()) {
/* global matching ('g') */
bool testGlobal = flags & TEST_GLOBAL_BIT;
if (rep.reobj())
rep.reobj()->asRegExp()->zeroLastIndex();
RegExpExecType type = flags & TEST_GLOBAL_BIT ? RegExpTest : RegExpExec;
if (RegExpObject *reobj = regExpPair.reobj())
reobj->zeroLastIndex();
for (size_t count = 0, i = 0, length = str->length(); i <= length; ++count) {
if (!re.execute(cx, res, str, &i, testGlobal, rval))
if (!ExecuteRegExp(cx, res, rep, linearStr, chars, length, &i, type, rval))
return false;
if (!Matched(testGlobal, *rval))
if (!Matched(type, *rval))
break;
if (!callback(cx, res, count, data))
return false;
@ -1484,12 +1498,12 @@ DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, const RegExpPair &rep,
}
} else {
/* single match */
bool testSingle = !!(flags & TEST_SINGLE_BIT),
callbackOnSingle = !!(flags & CALLBACK_ON_SINGLE_BIT);
RegExpExecType type = (flags & TEST_SINGLE_BIT) ? RegExpTest : RegExpExec;
bool callbackOnSingle = !!(flags & CALLBACK_ON_SINGLE_BIT);
size_t i = 0;
if (!re.execute(cx, res, str, &i, testSingle, rval))
if (!ExecuteRegExp(cx, res, rep, linearStr, chars, length, &i, type, rval))
return false;
if (callbackOnSingle && Matched(testSingle, *rval) && !callback(cx, res, 0, data))
if (callbackOnSingle && Matched(type, *rval) && !callback(cx, res, 0, data))
return false;
}
return true;
@ -1544,7 +1558,7 @@ js::str_match(JSContext *cx, uintN argc, Value *vp)
JSString *str = ThisToStringForStringProto(cx, vp);
if (!str)
return false;
RegExpGuard g(cx);
if (!g.init(argc, vp, true))
return false;
@ -1564,7 +1578,7 @@ js::str_match(JSContext *cx, uintN argc, Value *vp)
if (!DoMatch(cx, res, str, *rep, MatchCallback, arg, MATCH_ARGS, &rval))
return false;
if (rep->re().global())
if (rep->getPrivate()->global())
vp->setObjectOrNull(array.object());
else
*vp = rval;
@ -1587,14 +1601,20 @@ js::str_search(JSContext *cx, uintN argc, Value *vp)
}
if (cx->isExceptionPending()) /* from tryFlatMatch */
return false;
const RegExpPair *rep = g.normalizeRegExp(false, 1, argc, vp);
if (!rep)
return false;
JSLinearString *linearStr = str->ensureLinear(cx);
if (!linearStr)
return false;
const jschar *chars = linearStr->chars();
size_t length = linearStr->length();
RegExpStatics *res = cx->regExpStatics();
/* Per ECMAv5 15.5.4.12 (5) The last index property is ignored and left unchanged. */
size_t i = 0;
if (!rep->re().execute(cx, res, str, &i, true, vp))
if (!ExecuteRegExp(cx, res, rep->getPrivate(), linearStr, chars, length, &i, RegExpTest, vp))
return false;
if (vp->isTrue())
@ -1657,7 +1677,7 @@ InterpretDollar(JSContext *cx, RegExpStatics *res, const jschar *dp, const jscha
JS_ASSERT(num <= res->parenCount());
/*
/*
* Note: we index to get the paren with the (1-indexed) pair
* number, as opposed to a (0-indexed) paren number.
*/
@ -1815,7 +1835,7 @@ FindReplaceLength(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, size_t
return true;
}
/*
/*
* Precondition: |rdata.sb| already has necessary growth space reserved (as
* derived from FindReplaceLength).
*/
@ -2212,7 +2232,7 @@ js::str_replace(JSContext *cx, uintN argc, Value *vp)
if (rdata.lambda)
return str_replace_flat_lambda(cx, argc, vp, rdata, *fm);
/*
/*
* Note: we could optimize the text.length == pattern.length case if we wanted,
* even in the presence of dollar metachars.
*/
@ -2383,11 +2403,11 @@ SplitHelper(JSContext *cx, JSLinearString *str, uint32 limit, Matcher splitMatch
*/
class SplitRegExpMatcher {
RegExpStatics *res;
RegExpPrivate *re;
RegExpPrivate *rep;
public:
static const bool returnsCaptures = true;
SplitRegExpMatcher(RegExpPrivate *re, RegExpStatics *res) : res(res), re(re) {}
SplitRegExpMatcher(RegExpPrivate *rep, RegExpStatics *res) : res(res), rep(rep) {}
inline bool operator()(JSContext *cx, JSLinearString *str, size_t index,
SplitMatchResult *result) {
@ -2396,7 +2416,9 @@ class SplitRegExpMatcher {
= UndefinedValue()
#endif
;
if (!re->execute(cx, res, str, &index, true, &rval))
const jschar *chars = str->chars();
size_t length = str->length();
if (!ExecuteRegExp(cx, res, rep, str, chars, length, &index, RegExpTest, &rval))
return false;
if (!rval.isTrue()) {
result->setFailure();
@ -2465,7 +2487,9 @@ js::str_split(JSContext *cx, uintN argc, Value *vp)
bool sepUndefined = (argc == 0 || vp[2].isUndefined());
if (!sepUndefined) {
if (ValueIsRegExp(vp[2])) {
re = vp[2].toObject().asRegExp()->getPrivate();
re = vp[2].toObject().asRegExp()->getOrCreatePrivate(cx);
if (!re)
return false;
} else {
JSString *sep = js_ValueToString(cx, vp[2]);
if (!sep)
@ -2546,7 +2570,7 @@ str_substr(JSContext *cx, uintN argc, Value *vp)
if (argc == 1 || vp[3].isUndefined()) {
len = length - begin;
} else {
if (!ValueToIntegerRange(cx, vp[3], &len))
if (!ValueToIntegerRange(cx, vp[3], &len))
return false;
if (len <= 0) {

Просмотреть файл

@ -6593,9 +6593,9 @@ mjit::Compiler::jsop_regexp()
return;
}
RegExpPrivate *regexp = static_cast<RegExpObject *>(obj)->getPrivate();
RegExpObject *reobj = obj->asRegExp();
DebugOnly<uint32> origFlags = regexp->getFlags();
DebugOnly<uint32> origFlags = reobj->getFlags();
DebugOnly<uint32> staticsFlags = res->getFlags();
JS_ASSERT((origFlags & staticsFlags) == staticsFlags);
@ -6647,10 +6647,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 = regexp->addressOfRefCount();
masm.add32(Imm32(1), AbsoluteAddress(refcount));
frame.pushTypedPayload(JSVAL_TYPE_OBJECT, result);
stubcc.rejoin(Changes(1));

125
js/src/vm/MatchPairs.h Normal file
Просмотреть файл

@ -0,0 +1,125 @@
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sw=4 et tw=99 ft=cpp:
*
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla SpiderMonkey JavaScript code.
*
* The Initial Developer of the Original Code is
* the Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Leary <cdleary@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef MatchPairs_h__
#define MatchPairs_h__
/*
* RegExp match results are succinctly represented by pairs of integer
* indices delimiting (start, limit] segments of the input string.
*
* The pair count for a given RegExp match is the capturing parentheses
* count plus one for the "0 capturing paren" whole text match.
*/
namespace js {
struct MatchPair
{
int start;
int limit;
MatchPair(int start, int limit) : start(start), limit(limit) {}
size_t length() const {
JS_ASSERT(!isUndefined());
return limit - start;
}
bool isUndefined() const {
return start == -1;
}
void check() const {
JS_ASSERT(limit >= start);
JS_ASSERT_IF(!isUndefined(), start >= 0);
}
};
class MatchPairs
{
size_t pairCount_;
int buffer_[1];
explicit MatchPairs(size_t pairCount) : pairCount_(pairCount) {
initPairValues();
}
void initPairValues() {
for (int *it = buffer_; it < buffer_ + 2 * pairCount_; ++it)
*it = -1;
}
static size_t calculateSize(size_t backingPairCount) {
return sizeof(MatchPairs) - sizeof(int) + sizeof(int) * backingPairCount;
}
int *buffer() { return buffer_; }
friend class RegExpPrivate;
public:
/*
* |backingPairCount| is necessary because PCRE uses extra space
* after the actual results in the buffer.
*/
static MatchPairs *create(LifoAlloc &alloc, size_t pairCount, size_t backingPairCount);
size_t pairCount() const { return pairCount_; }
MatchPair pair(size_t i) {
JS_ASSERT(i < pairCount());
return MatchPair(buffer_[2 * i], buffer_[2 * i + 1]);
}
void displace(size_t amount) {
if (!amount)
return;
for (int *it = buffer_; it < buffer_ + 2 * pairCount_; ++it)
*it = *it < 0 ? -1 : *it + amount;
}
inline void checkAgainst(size_t length);
};
} /* namespace js */
#endif

Просмотреть файл

@ -52,49 +52,11 @@ inline js::RegExpObject *
JSObject::asRegExp()
{
JS_ASSERT(isRegExp());
js::RegExpObject *reobj = static_cast<js::RegExpObject *>(this);
JS_ASSERT(reobj->getPrivate());
return reobj;
return static_cast<js::RegExpObject *>(this);
}
namespace js {
/*
* Maintains the post-initialization invariant of having a RegExpPrivate.
*
* N.B. If initialization fails, the |RegExpPrivate| will be null, so
* finalization must consider that as a possibility.
*/
class PreInitRegExpObject
{
RegExpObject *reobj;
DebugOnly<bool> gotResult;
public:
explicit PreInitRegExpObject(JSObject *obj) {
JS_ASSERT(obj->isRegExp());
reobj = static_cast<RegExpObject *>(obj);
gotResult = false;
}
~PreInitRegExpObject() {
JS_ASSERT(gotResult);
}
RegExpObject *get() { return reobj; }
void succeed() {
JS_ASSERT(!gotResult);
JS_ASSERT(reobj->getPrivate());
gotResult = true;
}
void fail() {
JS_ASSERT(!gotResult);
gotResult = true;
}
};
inline bool
ValueIsRegExp(const Value &v)
{
@ -125,67 +87,69 @@ HasRegExpMetaChars(const jschar *chars, size_t length)
return false;
}
inline bool
ResetRegExpObject(JSContext *cx, RegExpObject *reobj, JSString *str, RegExpFlag flags)
{
AlreadyIncRefed<RegExpPrivate> rep = RegExpPrivate::create(cx, str, flags, NULL);
if (!rep)
return false;
return reobj->reset(cx, rep);
}
inline bool
ResetRegExpObject(JSContext *cx, RegExpObject *reobj, AlreadyIncRefed<RegExpPrivate> rep)
{
return reobj->reset(cx, rep);
}
inline RegExpObject *
RegExpObject::create(JSContext *cx, RegExpStatics *res, const jschar *chars, size_t length,
RegExpFlag flags, TokenStream *ts)
RegExpFlag flags, TokenStream *tokenStream)
{
RegExpFlag staticsFlags = res->getFlags();
return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), ts);
return createNoStatics(cx, chars, length, RegExpFlag(flags | staticsFlags), tokenStream);
}
inline RegExpObject *
RegExpObject::createNoStatics(JSContext *cx, const jschar *chars, size_t length,
RegExpFlag flags, TokenStream *ts)
RegExpFlag flags, TokenStream *tokenStream)
{
JSString *str = js_NewStringCopyN(cx, chars, length);
if (!str)
JSLinearString *source = js_NewStringCopyN(cx, chars, length);
if (!source)
return NULL;
/*
* |NewBuiltinClassInstance| can GC before we store |re| in the
* private field of the object. At that point the only reference to
* the source string could be from the malloc-allocated GC-invisible
* |re|. So we must anchor.
*/
JS::Anchor<JSString *> anchor(str);
AlreadyIncRefed<RegExpPrivate> rep = RegExpPrivate::create(cx, str, flags, ts);
if (!rep)
/* |NewBuiltinClassInstance| can GC. */
JS::Anchor<JSString *> anchor(source);
if (!RegExpPrivateCode::checkSyntax(cx, tokenStream, source))
return NULL;
JSObject *obj = NewBuiltinClassInstance(cx, &RegExpClass);
if (!obj)
return NULL;
PreInitRegExpObject pireo(obj);
RegExpObject *reobj = pireo.get();
if (!ResetRegExpObject(cx, reobj, rep)) {
rep->decref(cx);
pireo.fail();
return NULL;
}
RegExpObject *reobj = obj->asRegExp();
return reobj->reset(cx, source, flags) ? reobj : NULL;
}
pireo.succeed();
return reobj;
inline void
RegExpObject::finalize(JSContext *cx)
{
if (RegExpPrivate *rep = getPrivate())
rep->decref(cx);
#ifdef DEBUG
setPrivate((void *) 0x1); /* Non-null but still in the zero page. */
#endif
}
inline bool
RegExpObject::reset(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep)
{
if (!reset(cx, rep->getSource(), rep->getFlags()))
return false;
setPrivate(rep.get());
return true;
}
inline bool
RegExpObject::reset(JSContext *cx, RegExpObject *other)
{
if (RegExpPrivate *rep = other->getPrivate()) {
rep->incref(cx);
return reset(cx, AlreadyIncRefed<RegExpPrivate>(rep));
}
return reset(cx, other->getSource(), other->getFlags());
}
inline bool
RegExpObject::reset(JSContext *cx, JSLinearString *source, RegExpFlag flags)
{
if (nativeEmpty()) {
const js::Shape **shapep = &cx->compartment->initialRegExpShape;
@ -208,104 +172,20 @@ RegExpObject::reset(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep)
MULTILINE_FLAG_SLOT);
JS_ASSERT(nativeLookup(cx, ATOM_TO_JSID(atomState->stickyAtom))->slot == STICKY_FLAG_SLOT);
setPrivate(rep.get());
zeroLastIndex();
setSource(rep->getSource());
setGlobal(rep->global());
setIgnoreCase(rep->ignoreCase());
setMultiline(rep->multiline());
setSticky(rep->sticky());
setPrivate(NULL);
setSource(source);
setGlobal(flags & GlobalFlag);
setIgnoreCase(flags & IgnoreCaseFlag);
setMultiline(flags & MultilineFlag);
setSticky(flags & StickyFlag);
return true;
}
/* RegExpPrivate inlines. */
class RegExpMatchBuilder
{
JSContext * const cx;
JSObject * const array;
bool setProperty(JSAtom *name, Value v) {
return !!js_DefineProperty(cx, array, ATOM_TO_JSID(name), &v,
JS_PropertyStub, JS_StrictPropertyStub, JSPROP_ENUMERATE);
}
public:
RegExpMatchBuilder(JSContext *cx, JSObject *array) : cx(cx), array(array) {}
bool append(uint32 index, Value v) {
JS_ASSERT(!array->getOps()->getElement);
return !!js_DefineElement(cx, array, index, &v, JS_PropertyStub, JS_StrictPropertyStub,
JSPROP_ENUMERATE);
}
bool setIndex(int index) {
return setProperty(cx->runtime->atomState.indexAtom, Int32Value(index));
}
bool setInput(JSString *str) {
JS_ASSERT(str);
return setProperty(cx->runtime->atomState.inputAtom, StringValue(str));
}
};
inline void
RegExpPrivate::checkMatchPairs(JSString *input, int *buf, size_t matchItemCount)
{
#if DEBUG
size_t inputLength = input->length();
for (size_t i = 0; i < matchItemCount; i += 2) {
int start = buf[i];
int limit = buf[i + 1];
JS_ASSERT(limit >= start); /* Limit index must be larger than the start index. */
if (start == -1)
continue;
JS_ASSERT(start >= 0);
JS_ASSERT(size_t(limit) <= inputLength);
}
#endif
}
inline JSObject *
RegExpPrivate::createResult(JSContext *cx, JSString *input, int *buf, size_t matchItemCount)
{
/*
* Create the result array for a match. Array contents:
* 0: matched string
* 1..pairCount-1: paren matches
*/
JSObject *array = NewSlowEmptyArray(cx);
if (!array)
return NULL;
RegExpMatchBuilder builder(cx, array);
for (size_t i = 0; i < matchItemCount; i += 2) {
int start = buf[i];
int end = buf[i + 1];
JSString *captured;
if (start >= 0) {
JS_ASSERT(start <= end);
JS_ASSERT(unsigned(end) <= input->length());
captured = js_NewDependentString(cx, input, start, end - start);
if (!captured || !builder.append(i / 2, StringValue(captured)))
return NULL;
} else {
/* Missing parenthesized match. */
JS_ASSERT(i != 0); /* Since we had a match, first pair must be present. */
if (!builder.append(i / 2, UndefinedValue()))
return NULL;
}
}
if (!builder.setIndex(buf[0]) || !builder.setInput(input))
return NULL;
return array;
}
inline AlreadyIncRefed<RegExpPrivate>
RegExpPrivate::create(JSContext *cx, JSString *source, RegExpFlag flags, TokenStream *ts)
RegExpPrivate::create(JSContext *cx, JSLinearString *source, RegExpFlag flags, TokenStream *ts)
{
typedef AlreadyIncRefed<RegExpPrivate> RetType;
@ -416,8 +296,8 @@ RegExpPrivate::compile(JSContext *cx, TokenStream *ts)
return code.compile(cx, *fakeySource, ts, &parenCount, getFlags());
}
inline RegExpPrivateCode::ExecuteResult
RegExpPrivateCode::execute(JSContext *cx, const jschar *chars, size_t start, size_t length,
inline RegExpRunStatus
RegExpPrivateCode::execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
int *output, size_t outputCount)
{
int result;
@ -432,17 +312,17 @@ RegExpPrivateCode::execute(JSContext *cx, const jschar *chars, size_t start, siz
#endif
if (result == -1)
return Success_NotFound;
return RegExpRunStatus_Success_NotFound;
#if !ENABLE_YARR_JIT
if (result < 0) {
reportPCREError(cx, result);
return Error;
return RegExpRunStatus_Error;
}
#endif
JS_ASSERT(result >= 0);
return Success;
return RegExpRunStatus_Success;
}
inline void
@ -464,6 +344,15 @@ RegExpPrivate::decref(JSContext *cx)
cx->delete_(this);
}
inline RegExpPrivate *
RegExpObject::getOrCreatePrivate(JSContext *cx)
{
if (RegExpPrivate *rep = getPrivate())
return rep;
return makePrivate(cx) ? getPrivate() : NULL;
}
inline RegExpPrivate *
RegExpObject::getPrivate() const
{

Просмотреть файл

@ -41,6 +41,7 @@
#include "jsscan.h"
#include "vm/RegExpStatics.h"
#include "vm/MatchPairs.h"
#include "jsobjinlines.h"
#include "jsstrinlines.h"
@ -59,90 +60,99 @@ JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB);
JS_STATIC_ASSERT(MultilineFlag == JSREG_MULTILINE);
JS_STATIC_ASSERT(StickyFlag == JSREG_STICKY);
bool
RegExpPrivate::executeInternal(JSContext *cx, RegExpStatics *res, JSString *inputstr,
size_t *lastIndex, bool test, Value *rval)
MatchPairs *
MatchPairs::create(LifoAlloc &alloc, size_t pairCount, size_t backingPairCount)
{
const size_t pairCount = parenCount + 1;
const size_t matchItemCount = pairCount * 2;
const size_t bufCount = RegExpPrivateCode::getOutputSize(pairCount);
void *mem = alloc.alloc(calculateSize(backingPairCount));
if (!mem)
return NULL;
LifoAllocScope las(&cx->tempLifoAlloc());
int *buf = cx->tempLifoAlloc().newArray<int>(bufCount);
if (!buf)
return false;
return new (mem) MatchPairs(pairCount);
}
inline void
MatchPairs::checkAgainst(size_t inputLength)
{
#if DEBUG
for (size_t i = 0; i < pairCount(); ++i) {
MatchPair p = pair(i);
p.check();
if (p.isUndefined())
continue;
JS_ASSERT(size_t(p.limit) <= inputLength);
}
#endif
}
RegExpRunStatus
RegExpPrivate::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
LifoAllocScope &allocScope, MatchPairs **output)
{
const size_t origLength = length;
size_t backingPairCount = RegExpPrivateCode::getOutputSize(pairCount());
MatchPairs *matchPairs = MatchPairs::create(allocScope.alloc(), pairCount(), backingPairCount);
if (!matchPairs)
return RegExpRunStatus_Error;
/*
* The JIT regexp procedure doesn't always initialize matchPair values.
* Maybe we can make this faster by ensuring it does?
* |displacement| emulates sticky mode by matching from this offset
* into the char buffer and subtracting the delta off at the end.
*/
for (int *it = buf; it != buf + matchItemCount; ++it)
*it = -1;
JSLinearString *input = inputstr->ensureLinear(cx);
if (!input)
return false;
JS::Anchor<JSString *> anchor(input);
size_t len = input->length();
const jschar *chars = input->chars();
/*
* inputOffset emulates sticky mode by matching from this offset into the char buf and
* subtracting the delta off at the end.
*/
size_t inputOffset = 0;
size_t start = *lastIndex;
size_t displacement = 0;
if (sticky()) {
/* Sticky matches at the last index for the regexp object. */
chars += *lastIndex;
len -= *lastIndex;
inputOffset = *lastIndex;
displacement = *lastIndex;
chars += displacement;
length -= displacement;
start = 0;
}
size_t start = *lastIndex - inputOffset;
RegExpPrivateCode::ExecuteResult result = code.execute(cx, chars, start, len, buf, bufCount);
RegExpRunStatus status = code.execute(cx, chars, length, start,
matchPairs->buffer(), backingPairCount);
switch (result) {
case RegExpPrivateCode::Error:
return false;
case RegExpPrivateCode::Success_NotFound:
*rval = NullValue();
return true;
switch (status) {
case RegExpRunStatus_Error:
return status;
case RegExpRunStatus_Success_NotFound:
*output = matchPairs;
return status;
default:
JS_ASSERT(result == RegExpPrivateCode::Success);
JS_ASSERT(status == RegExpRunStatus_Success);
}
/*
* Adjust buf for the inputOffset. Use of sticky is rare and the matchItemCount is small, so
* just do another pass.
*/
if (JS_UNLIKELY(inputOffset)) {
for (size_t i = 0; i < matchItemCount; ++i)
buf[i] = buf[i] < 0 ? -1 : buf[i] + inputOffset;
}
matchPairs->displace(displacement);
matchPairs->checkAgainst(origLength);
/* Make sure the populated contents of |buf| are sane values against |input|. */
checkMatchPairs(input, buf, matchItemCount);
*lastIndex = matchPairs->pair(0).limit;
*output = matchPairs;
if (res)
res->updateFromMatch(cx, input, buf, matchItemCount);
return RegExpRunStatus_Success;
}
*lastIndex = buf[1];
if (test) {
*rval = BooleanValue(true);
return true;
}
JSObject *array = createResult(cx, input, buf, matchItemCount);
if (!array)
bool
RegExpObject::makePrivate(JSContext *cx)
{
JS_ASSERT(!getPrivate());
AlreadyIncRefed<RegExpPrivate> rep = RegExpPrivate::create(cx, getSource(), getFlags(), NULL);
if (!rep)
return false;
*rval = ObjectValue(*array);
setPrivate(rep.get());
return true;
}
RegExpRunStatus
RegExpObject::execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
LifoAllocScope &allocScope, MatchPairs **output)
{
if (!getPrivate() && !makePrivate(cx))
return RegExpRunStatus_Error;
return getPrivate()->execute(cx, chars, length, lastIndex, allocScope, output);
}
const Shape *
RegExpObject::assignInitialShape(JSContext *cx)
{
@ -226,8 +236,7 @@ js_XDRRegExpObject(JSXDRState *xdr, JSObject **objp)
static void
regexp_finalize(JSContext *cx, JSObject *obj)
{
if (RegExpPrivate *rep = static_cast<RegExpObject *>(obj)->getPrivate())
rep->decref(cx);
obj->asRegExp()->finalize(cx);
}
Class js::RegExpClass = {
@ -294,9 +303,9 @@ RegExpPrivateCode::reportPCREError(JSContext *cx, int error)
return
switch (error) {
case -2: REPORT(JSMSG_REGEXP_TOO_COMPLEX);
case 0: JS_NOT_REACHED("Precondition violation: an error must have occurred.");
case 0: JS_NOT_REACHED("Precondition violation: an error must have occurred.");
case 1: REPORT(JSMSG_TRAILING_SLASH);
case 2: REPORT(JSMSG_TRAILING_SLASH);
case 2: REPORT(JSMSG_TRAILING_SLASH);
case 3: REPORT(JSMSG_REGEXP_TOO_COMPLEX);
case 4: REPORT(JSMSG_BAD_QUANTIFIER);
case 5: REPORT(JSMSG_BAD_QUANTIFIER);
@ -317,6 +326,7 @@ RegExpPrivateCode::reportPCREError(JSContext *cx, int error)
}
#undef REPORT
}
#endif /* ENABLE_YARR_JIT */
bool
@ -357,7 +367,7 @@ js::ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut)
}
AlreadyIncRefed<RegExpPrivate>
RegExpPrivate::createFlagged(JSContext *cx, JSString *str, JSString *opt, TokenStream *ts)
RegExpPrivate::create(JSContext *cx, JSLinearString *str, JSString *opt, TokenStream *ts)
{
if (!opt)
return create(cx, str, RegExpFlag(0), ts);
@ -370,7 +380,7 @@ RegExpPrivate::createFlagged(JSContext *cx, JSString *str, JSString *opt, TokenS
}
RegExpObject *
RegExpObject::clone(JSContext *cx, RegExpObject *obj, RegExpObject *proto)
RegExpObject::clone(JSContext *cx, RegExpObject *reobj, RegExpObject *proto)
{
JSObject *clone = NewNativeClassInstance(cx, &RegExpClass, proto, proto->getParent());
if (!clone)
@ -380,41 +390,35 @@ RegExpObject::clone(JSContext *cx, RegExpObject *obj, RegExpObject *proto)
* This clone functionality does not duplicate the JIT'd code blob,
* which is necessary for cross-compartment cloning functionality.
*/
assertSameCompartment(cx, obj, clone);
assertSameCompartment(cx, reobj, clone);
RegExpStatics *res = cx->regExpStatics();
RegExpObject *reclone = clone->asRegExp();
/*
/*
* Check that the RegExpPrivate for the original is okay to use in
* the clone -- if the RegExpStatics provides more flags we'll need
* a different RegExpPrivate.
* the clone -- if the |RegExpStatics| provides more flags we'll
* need a different |RegExpPrivate|.
*/
AlreadyIncRefed<RegExpPrivate> rep(NULL);
{
RegExpFlag origFlags = obj->getFlags();
RegExpFlag staticsFlags = res->getFlags();
if ((origFlags & staticsFlags) != staticsFlags) {
RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags);
rep = RegExpPrivate::create(cx, obj->getSource(), newFlags, NULL);
if (!rep)
return NULL;
} else {
RegExpPrivate *toShare = obj->getPrivate();
toShare->incref(cx);
rep = AlreadyIncRefed<RegExpPrivate>(toShare);
RegExpFlag origFlags = reobj->getFlags();
RegExpFlag staticsFlags = res->getFlags();
if ((origFlags & staticsFlags) != staticsFlags) {
RegExpFlag newFlags = RegExpFlag(origFlags | staticsFlags);
return reclone->reset(cx, reobj->getSource(), newFlags) ? reclone : NULL;
}
RegExpPrivate *toShare = reobj->getPrivate();
if (toShare) {
toShare->incref(cx);
if (!reclone->reset(cx, AlreadyIncRefed<RegExpPrivate>(toShare))) {
toShare->decref(cx);
return NULL;
}
} else {
if (!reclone->reset(cx, reobj))
return NULL;
}
JS_ASSERT(rep);
PreInitRegExpObject pireo(clone);
RegExpObject *reclone = pireo.get();
if (!ResetRegExpObject(cx, reclone, rep)) {
pireo.fail();
return NULL;
}
pireo.succeed();
return reclone;
}
@ -435,11 +439,7 @@ JS_DEFINE_CALLINFO_3(extern, OBJECT, js_CloneRegExpObject, CONTEXT, OBJECT, OBJE
JSFlatString *
RegExpObject::toString(JSContext *cx) const
{
RegExpPrivate *rep = getPrivate();
if (!rep)
return cx->runtime->emptyString;
JSLinearString *src = rep->getSource();
JSLinearString *src = getSource();
StringBuffer sb(cx);
if (size_t len = src->length()) {
if (!sb.reserve(len + 2))
@ -451,13 +451,13 @@ RegExpObject::toString(JSContext *cx) const
if (!sb.append("/(?:)/"))
return NULL;
}
if (rep->global() && !sb.append('g'))
if (global() && !sb.append('g'))
return NULL;
if (rep->ignoreCase() && !sb.append('i'))
if (ignoreCase() && !sb.append('i'))
return NULL;
if (rep->multiline() && !sb.append('m'))
if (multiline() && !sb.append('m'))
return NULL;
if (rep->sticky() && !sb.append('y'))
if (sticky() && !sb.append('y'))
return NULL;
return sb.finishString();

Просмотреть файл

@ -49,13 +49,19 @@
#include "yarr/Yarr.h"
#if ENABLE_YARR_JIT
#include "yarr/YarrJIT.h"
#include "yarr/YarrSyntaxChecker.h"
#else
#include "yarr/pcre/pcre.h"
#endif
namespace js {
class RegExpPrivate;
enum RegExpRunStatus
{
RegExpRunStatus_Error,
RegExpRunStatus_Success,
RegExpRunStatus_Success_NotFound
};
class RegExpObject : public ::JSObject
{
@ -87,6 +93,20 @@ class RegExpObject : public ::JSObject
/* Note: fallible. */
JSFlatString *toString(JSContext *cx) const;
/*
* Run the regular expression over the input text.
*
* Results are placed in |output| as integer pairs. For eaxmple,
* |output[0]| and |output[1]| represent the text indices that make
* up the "0" (whole match) pair. Capturing parens will result in
* more output.
*
* 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);
/* Accessors. */
const Value &getLastIndex() const {
@ -102,21 +122,20 @@ class RegExpObject : public ::JSObject
setSlot(LAST_INDEX_SLOT, Int32Value(0));
}
JSString *getSource() const {
return getSlot(SOURCE_SLOT).toString();
JSLinearString *getSource() const {
return &getSlot(SOURCE_SLOT).toString()->asLinear();
}
void setSource(JSString *source) {
void setSource(JSLinearString *source) {
setSlot(SOURCE_SLOT, StringValue(source));
}
RegExpFlag getFlags() const {
uintN flags = 0;
flags |= getSlot(GLOBAL_FLAG_SLOT).toBoolean() ? GlobalFlag : 0;
flags |= getSlot(IGNORE_CASE_FLAG_SLOT).toBoolean() ? IgnoreCaseFlag : 0;
flags |= getSlot(MULTILINE_FLAG_SLOT).toBoolean() ? MultilineFlag : 0;
flags |= getSlot(STICKY_FLAG_SLOT).toBoolean() ? StickyFlag : 0;
flags |= global() ? GlobalFlag : 0;
flags |= ignoreCase() ? IgnoreCaseFlag : 0;
flags |= multiline() ? MultilineFlag : 0;
flags |= sticky() ? StickyFlag : 0;
return RegExpFlag(flags);
}
inline RegExpPrivate *getPrivate() const;
/* Flags. */
@ -124,12 +143,34 @@ class RegExpObject : public ::JSObject
void setGlobal(bool enabled) { setSlot(GLOBAL_FLAG_SLOT, BooleanValue(enabled)); }
void setMultiline(bool enabled) { setSlot(MULTILINE_FLAG_SLOT, BooleanValue(enabled)); }
void setSticky(bool enabled) { setSlot(STICKY_FLAG_SLOT, BooleanValue(enabled)); }
bool ignoreCase() const { return getSlot(IGNORE_CASE_FLAG_SLOT).toBoolean(); }
bool global() const { return getSlot(GLOBAL_FLAG_SLOT).toBoolean(); }
bool multiline() const { return getSlot(MULTILINE_FLAG_SLOT).toBoolean(); }
bool sticky() const { return getSlot(STICKY_FLAG_SLOT).toBoolean(); }
/*
* N.B. |RegExpObject|s can be mutated in place because of |RegExp.prototype.compile|, hence
* |reset| for re-initialization.
*/
inline bool reset(JSContext *cx, RegExpObject *other);
inline bool reset(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep);
inline bool reset(JSContext *cx, JSLinearString *source, RegExpFlag flags);
inline RegExpPrivate *getOrCreatePrivate(JSContext *cx);
inline void finalize(JSContext *cx);
private:
/* N.B. |RegExpObject|s can be mutated in place because of |RegExp.prototype.compile|. */
inline bool reset(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep);
/* The |RegExpPrivate| is lazily created at the time of use. */
inline RegExpPrivate *getPrivate() const;
friend bool ResetRegExpObject(JSContext *, RegExpObject *, JSString *, RegExpFlag);
/*
* Precondition: the syntax for |source| has already been validated.
* Side effect: sets the private field.
*/
bool makePrivate(JSContext *cx);
friend bool ResetRegExpObject(JSContext *, RegExpObject *, JSLinearString *, RegExpFlag);
friend bool ResetRegExpObject(JSContext *, RegExpObject *, AlreadyIncRefed<RegExpPrivate>);
/*
@ -143,13 +184,6 @@ class RegExpObject : public ::JSObject
RegExpObject &operator=(const RegExpObject &reo);
}; /* class RegExpObject */
inline bool
ResetRegExpObject(JSContext *cx, RegExpObject *reobj, JSString *str, RegExpFlag flags);
/* N.B. On failure, caller must decref |rep|. */
inline bool
ResetRegExpObject(JSContext *cx, AlreadyIncRefed<RegExpPrivate> rep);
/* Abstracts away the gross |RegExpPrivate| backend details. */
class RegExpPrivateCode
{
@ -189,20 +223,31 @@ class RegExpPrivateCode
#endif
}
static bool checkSyntax(JSContext *cx, TokenStream *tokenStream, JSLinearString *source) {
#if ENABLE_YARR_JIT
ErrorCode error = JSC::Yarr::checkSyntax(*source);
if (error == JSC::Yarr::NoError)
return true;
reportYarrError(cx, tokenStream, error);
return false;
#else
# error "Syntax checking not implemented for !ENABLE_YARR_JIT"
#endif
}
#if ENABLE_YARR_JIT
static inline bool isJITRuntimeEnabled(JSContext *cx);
void reportYarrError(JSContext *cx, TokenStream *ts, JSC::Yarr::ErrorCode error);
static void reportYarrError(JSContext *cx, TokenStream *ts, JSC::Yarr::ErrorCode error);
#else
void reportPCREError(JSContext *cx, int error);
static void reportPCREError(JSContext *cx, int error);
#endif
inline bool compile(JSContext *cx, JSLinearString &pattern, TokenStream *ts, uintN *parenCount,
RegExpFlag flags);
enum ExecuteResult { Error, Success, Success_NotFound };
inline ExecuteResult execute(JSContext *cx, const jschar *chars, size_t start, size_t length,
int *output, size_t outputCount);
inline RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
int *output, size_t outputCount);
static size_t getOutputSize(size_t pairCount) {
#if ENABLE_YARR_JIT
@ -247,37 +292,16 @@ class RegExpPrivate
bool compile(JSContext *cx, TokenStream *ts);
static inline void checkMatchPairs(JSString *input, int *buf, size_t matchItemCount);
static JSObject *createResult(JSContext *cx, JSString *input, int *buf, size_t matchItemCount);
bool executeInternal(JSContext *cx, RegExpStatics *res, JSString *input,
size_t *lastIndex, bool test, Value *rval);
public:
/*
* Execute regexp on |input| at |*lastIndex|.
*
* On match: Update |*lastIndex| and RegExp class statics.
* Return true if test is true. Place an array in |*rval| if test is false.
* On mismatch: Make |*rval| null.
*/
bool execute(JSContext *cx, RegExpStatics *res, JSString *input, size_t *lastIndex, bool test,
Value *rval) {
JS_ASSERT(res);
return executeInternal(cx, res, input, lastIndex, test, rval);
}
static AlreadyIncRefed<RegExpPrivate>
create(JSContext *cx, JSLinearString *source, RegExpFlag flags, TokenStream *ts);
bool executeNoStatics(JSContext *cx, JSString *input, size_t *lastIndex, bool test,
Value *rval) {
return executeInternal(cx, NULL, input, lastIndex, test, rval);
}
static AlreadyIncRefed<RegExpPrivate>
create(JSContext *cx, JSLinearString *source, JSString *flags, TokenStream *ts);
/* Factories */
static AlreadyIncRefed<RegExpPrivate> create(JSContext *cx, JSString *source, RegExpFlag flags,
TokenStream *ts);
/* Would overload |create|, but |0| resolves ambiguously against pointer and uint. */
static AlreadyIncRefed<RegExpPrivate> createFlagged(JSContext *cx, JSString *source,
JSString *flags, TokenStream *ts);
RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
LifoAllocScope &allocScope, MatchPairs **output);
/* Mutators */
@ -289,14 +313,18 @@ class RegExpPrivate
/* Accessors */
JSLinearString *getSource() const { return source; }
size_t getParenCount() const { return parenCount; }
RegExpFlag getFlags() const { return flags; }
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; }
}; /* class RegExpPrivate */
};
/*
* Parse regexp flags. Report an error and return false if an invalid

Просмотреть файл

@ -45,12 +45,14 @@
#include "js/Vector.h"
#include "vm/MatchPairs.h"
namespace js {
class RegExpStatics
{
typedef Vector<int, 20, SystemAllocPolicy> MatchPairs;
MatchPairs matchPairs;
typedef Vector<int, 20, SystemAllocPolicy> Pairs;
Pairs matchPairs;
/* The input that was used to produce matchPairs. */
JSLinearString *matchPairsInput;
/* The input last set on the statics. */
@ -162,17 +164,20 @@ class RegExpStatics
/* Mutators. */
bool updateFromMatch(JSContext *cx, JSLinearString *input, int *buf, size_t matchItemCount) {
bool updateFromMatchPairs(JSContext *cx, JSLinearString *input, MatchPairs *newPairs) {
JS_ASSERT(input);
aboutToWrite();
pendingInput = input;
if (!matchPairs.resizeUninitialized(matchItemCount)) {
if (!matchPairs.resizeUninitialized(2 * newPairs->pairCount())) {
js_ReportOutOfMemory(cx);
return false;
}
for (size_t i = 0; i < matchItemCount; ++i)
matchPairs[i] = buf[i];
for (size_t i = 0; i < newPairs->pairCount(); ++i) {
matchPairs[2 * i] = newPairs->pair(i).start;
matchPairs[2 * i + 1] = newPairs->pair(i).limit;
}
matchPairsInput = input;
return true;