Bug 701332: Don't expose RegExp privates. (r=Waldo)

This commit is contained in:
Chris Leary 2011-11-10 17:30:04 -08:00
parent 3b0417b8c4
commit ccc7d0d4ac
9 changed files with 176 additions and 79 deletions

View File

@ -159,11 +159,11 @@ ExecuteRegExpImpl(JSContext *cx, RegExpStatics *res, T *re, JSLinearString *inpu
}
bool
js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpPrivate *rep, JSLinearString *input,
js::ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpMatcher *matcher, 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);
return ExecuteRegExpImpl(cx, res, matcher, input, chars, length, lastIndex, type, rval);
}
bool
@ -278,7 +278,7 @@ CompileRegExpObject(JSContext *cx, RegExpObjectBuilder &builder,
if (!escapedSourceStr)
return false;
if (!RegExpPrivateCode::checkSyntax(cx, NULL, escapedSourceStr))
if (!CheckRegExpSyntax(cx, escapedSourceStr))
return false;
RegExpStatics *res = cx->regExpStatics();
@ -515,15 +515,10 @@ ExecuteRegExp(JSContext *cx, Native native, uintN argc, Value *vp)
return ok;
RegExpObject *reobj = obj->asRegExp();
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>(rep));
RegExpMatcher matcher(cx);
if (!matcher.reset(reobj))
return false;
RegExpStatics *res = cx->regExpStatics();
@ -548,7 +543,7 @@ ExecuteRegExp(JSContext *cx, Native native, uintN argc, Value *vp)
return false;
/* Steps 6-7 (with sticky extension). */
if (!rep->global() && !rep->sticky())
if (!matcher.global() && !matcher.sticky())
i = 0;
/* Step 9a. */
@ -561,13 +556,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, rep, linearInput, chars, length, &lastIndexInt, execType,
if (!ExecuteRegExp(cx, res, &matcher, linearInput, chars, length, &lastIndexInt, execType,
&args.rval())) {
return false;
}
/* Step 11 (with sticky extension). */
if (rep->global() || (!args.rval().isNull() && rep->sticky())) {
if (matcher.global() || (!args.rval().isNull() && matcher.sticky())) {
if (args.rval().isNull())
reobj->zeroLastIndex();
else

View File

@ -64,7 +64,7 @@ ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpObject *reobj, JSLinearSt
size_t *lastIndex, RegExpExecType type, Value *rval);
bool
ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpPrivate *rep, JSLinearString *input,
ExecuteRegExp(JSContext *cx, RegExpStatics *res, RegExpMatcher *matcher, JSLinearString *input,
const jschar *chars, size_t length,
size_t *lastIndex, RegExpExecType type, Value *rval);

View File

@ -122,11 +122,18 @@ struct FlatClosureData;
struct Class;
class RegExpObject;
class RegExpPrivate;
class RegExpMatcher;
class RegExpObjectBuilder;
class RegExpStatics;
class MatchPairs;
namespace detail {
class RegExpPrivate;
class RegExpPrivateCode;
} /* namespace detail */
enum RegExpFlag
{
IgnoreCaseFlag = 0x01,
@ -232,7 +239,7 @@ typedef HashMap<jsbytecode *, BreakpointSite *, DefaultHasher<jsbytecode *>, Run
class Debugger;
class WatchpointMap;
typedef HashMap<JSAtom *, RegExpPrivate *, DefaultHasher<JSAtom *>, RuntimeAllocPolicy>
typedef HashMap<JSAtom *, detail::RegExpPrivate *, DefaultHasher<JSAtom *>, RuntimeAllocPolicy>
RegExpPrivateCache;
typedef JSNative Native;

View File

@ -1271,38 +1271,33 @@ class FlatMatch
*/
class RegExpPair
{
JSContext *cx;
AutoRefCount<RegExpPrivate> rep_;
RegExpObject *reobj_;
JSContext *cx;
mutable RegExpMatcher matcher_;
RegExpObject *reobj_;
explicit RegExpPair(RegExpPair &);
void operator=(const RegExpPair &);
explicit RegExpPair(RegExpPair &) MOZ_DELETE;
void operator=(const RegExpPair &) MOZ_DELETE;
public:
explicit RegExpPair(JSContext *cx) : cx(cx), rep_(cx) {}
explicit RegExpPair(JSContext *cx) : cx(cx), matcher_(cx) {}
bool resetWithObject(JSContext *cx, RegExpObject *reobj) {
JS_ASSERT(cx == this->cx);
bool reset(RegExpObject *reobj) {
reobj_ = reobj;
RegExpPrivate *rep = reobj_->asRegExp()->getOrCreatePrivate(cx);
if (!rep)
return false;
rep_.reset(NeedsIncRef<RegExpPrivate>(rep));
return true;
return matcher_.reset(reobj);
}
void resetWithPrivate(AlreadyIncRefed<RegExpPrivate> rep) {
bool reset(JSLinearString *patstr, JSString *opt) {
reobj_ = NULL;
rep_.reset(rep);
return matcher_.reset(patstr, opt);
}
bool null() const { return rep_.null(); }
bool null() const { return matcher_.null(); }
RegExpObject *reobj() const { return reobj_; }
RegExpPrivate *getPrivate() const {
RegExpMatcher *getMatcher() const {
JS_ASSERT(!null());
return rep_.get();
return &matcher_;
}
};
@ -1356,7 +1351,8 @@ class RegExpGuard
init(uintN argc, Value *vp, bool convertVoid = false)
{
if (argc != 0 && ValueIsRegExp(vp[2])) {
rep.resetWithObject(cx, vp[2].toObject().asRegExp());
if (!rep.reset(vp[2].toObject().asRegExp()))
return false;
} else {
if (convertVoid && (argc == 0 || vp[2].isUndefined())) {
fm.patstr = cx->runtime->emptyString;
@ -1440,10 +1436,9 @@ class RegExpGuard
}
JS_ASSERT(patstr);
AlreadyIncRefed<RegExpPrivate> re = RegExpPrivate::create(cx, patstr, opt, NULL);
if (!re)
if (!rep.reset(patstr, opt))
return NULL;
rep.resetWithPrivate(re);
return &rep;
}
@ -1480,21 +1475,21 @@ static bool
DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, const RegExpPair &regExpPair,
DoMatchCallback callback, void *data, MatchControlFlags flags, Value *rval)
{
RegExpPrivate *rep = regExpPair.getPrivate();
RegExpMatcher *matcher = regExpPair.getMatcher();
JSLinearString *linearStr = str->ensureLinear(cx);
if (!linearStr)
return false;
const jschar *chars = linearStr->chars();
size_t length = linearStr->length();
if (rep->global()) {
if (matcher->global()) {
/* global matching ('g') */
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 (!ExecuteRegExp(cx, res, rep, linearStr, chars, length, &i, type, rval))
if (!ExecuteRegExp(cx, res, matcher, linearStr, chars, length, &i, type, rval))
return false;
if (!Matched(type, *rval))
break;
@ -1508,7 +1503,7 @@ DoMatch(JSContext *cx, RegExpStatics *res, JSString *str, const RegExpPair &regE
RegExpExecType type = (flags & TEST_SINGLE_BIT) ? RegExpTest : RegExpExec;
bool callbackOnSingle = !!(flags & CALLBACK_ON_SINGLE_BIT);
size_t i = 0;
if (!ExecuteRegExp(cx, res, rep, linearStr, chars, length, &i, type, rval))
if (!ExecuteRegExp(cx, res, matcher, linearStr, chars, length, &i, type, rval))
return false;
if (callbackOnSingle && Matched(type, *rval) && !callback(cx, res, 0, data))
return false;
@ -1583,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->getPrivate()->global())
if (rep->getMatcher()->global())
vp->setObjectOrNull(array.object());
else
*vp = rval;
@ -1619,7 +1614,7 @@ js::str_search(JSContext *cx, uintN argc, Value *vp)
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 (!ExecuteRegExp(cx, res, rep->getPrivate(), linearStr, chars, length, &i, RegExpTest, vp))
if (!ExecuteRegExp(cx, res, rep->getMatcher(), linearStr, chars, length, &i, RegExpTest, vp))
return false;
if (vp->isTrue())
@ -2407,11 +2402,11 @@ SplitHelper(JSContext *cx, JSLinearString *str, uint32 limit, Matcher splitMatch
*/
class SplitRegExpMatcher {
RegExpStatics *res;
RegExpPrivate *rep;
RegExpMatcher *matcher;
public:
static const bool returnsCaptures = true;
SplitRegExpMatcher(RegExpPrivate *rep, RegExpStatics *res) : res(res), rep(rep) {}
SplitRegExpMatcher(RegExpMatcher *matcher, RegExpStatics *res) : res(res), matcher(matcher) {}
inline bool operator()(JSContext *cx, JSLinearString *str, size_t index,
SplitMatchResult *result) {
@ -2422,7 +2417,7 @@ class SplitRegExpMatcher {
;
const jschar *chars = str->chars();
size_t length = str->length();
if (!ExecuteRegExp(cx, res, rep, str, chars, length, &index, RegExpTest, &rval))
if (!ExecuteRegExp(cx, res, matcher, str, chars, length, &index, RegExpTest, &rval))
return false;
if (!rval.isTrue()) {
result->setFailure();
@ -2486,14 +2481,13 @@ js::str_split(JSContext *cx, uintN argc, Value *vp)
}
/* Step 8. */
AutoRefCount<RegExpPrivate> rep(cx);
RegExpMatcher matcher(cx);
JSLinearString *sepstr = NULL;
bool sepUndefined = (argc == 0 || vp[2].isUndefined());
if (!sepUndefined) {
if (ValueIsRegExp(vp[2])) {
RegExpObject *reobj = vp[2].toObject().asRegExp();
rep.reset(NeedsIncRef<RegExpPrivate>(reobj->getOrCreatePrivate(cx)));
if (!rep)
if (!matcher.reset(reobj))
return false;
} else {
JSString *sep = js_ValueToString(cx, vp[2]);
@ -2533,12 +2527,12 @@ js::str_split(JSContext *cx, uintN argc, Value *vp)
/* Steps 11-15. */
JSObject *aobj;
if (rep) {
aobj = SplitHelper(cx, strlin, limit,
SplitRegExpMatcher(rep.get(), cx->regExpStatics()), type);
} else {
if (matcher.null()) {
// NB: sepstr is anchored through its storage in vp[2].
aobj = SplitHelper(cx, strlin, limit, SplitStringMatcher(sepstr), type);
} else {
aobj = SplitHelper(cx, strlin, limit,
SplitRegExpMatcher(&matcher, cx->regExpStatics()), type);
}
if (!aobj)
return false;

View File

@ -6982,11 +6982,12 @@ mjit::Compiler::jsop_regexp()
}
/*
* Force creation of the RegExpPrivate in the script's RegExpObject so we take it in the
* getNewObject template copy.
* Force creation of the RegExpPrivate 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.
*/
RegExpPrivate *rep = reobj->getOrCreatePrivate(cx);
if (!rep)
if (!reobj->makePrivateNow(cx))
return false;
RegisterID result = frame.allocReg();
@ -6999,7 +7000,7 @@ mjit::Compiler::jsop_regexp()
OOL_STUBCALL(stubs::RegExp, REJOIN_FALLTHROUGH);
/* Bump the refcount on the wrapped RegExp. */
size_t *refcount = rep->addressOfRefCount();
size_t *refcount = reobj->addressOfPrivateRefCount();
masm.add32(Imm32(1), AbsoluteAddress(refcount));
frame.pushTypedPayload(JSVAL_TYPE_OBJECT, result);

View File

@ -93,7 +93,7 @@ class MatchPairs
int *buffer() { return buffer_; }
friend class RegExpPrivate;
friend class detail::RegExpPrivate;
public:
/*

View File

@ -89,6 +89,13 @@ HasRegExpMetaChars(const jschar *chars, size_t length)
return false;
}
inline size_t *
RegExpObject::addressOfPrivateRefCount() const
{
JS_ASSERT(getPrivate());
return getPrivate()->addressOfRefCount();
}
inline void
RegExpObject::setPrivate(RegExpPrivate *rep)
{
@ -178,6 +185,16 @@ RegExpObject::init(JSContext *cx, JSLinearString *source, RegExpFlag flags)
return true;
}
inline bool
RegExpMatcher::reset(JSLinearString *patstr, JSString *opt)
{
AlreadyIncRefed<RegExpPrivate> priv = RegExpPrivate::create(cx, patstr, opt, NULL);
if (!priv)
return false;
arc.reset(priv);
return true;
}
inline void
RegExpObject::setLastIndex(const Value &v)
{
@ -228,8 +245,9 @@ RegExpObject::setSticky(bool enabled)
/* RegExpPrivate inlines. */
inline AlreadyIncRefed<RegExpPrivate>
RegExpPrivate::create(JSContext *cx, JSLinearString *source, RegExpFlag flags, TokenStream *ts)
inline AlreadyIncRefed<detail::RegExpPrivate>
detail::RegExpPrivate::create(JSContext *cx, JSLinearString *source, RegExpFlag flags,
TokenStream *ts)
{
typedef AlreadyIncRefed<RegExpPrivate> RetType;
@ -284,7 +302,7 @@ RegExpPrivate::create(JSContext *cx, JSLinearString *source, RegExpFlag flags, T
/* This function should be deleted once bad Android platforms phase out. See bug 604774. */
inline bool
RegExpPrivateCode::isJITRuntimeEnabled(JSContext *cx)
detail::RegExpPrivateCode::isJITRuntimeEnabled(JSContext *cx)
{
#if defined(ANDROID) && defined(JS_TRACER) && defined(JS_METHODJIT)
return cx->traceJitEnabled || cx->methodJitEnabled;
@ -294,8 +312,8 @@ RegExpPrivateCode::isJITRuntimeEnabled(JSContext *cx)
}
inline bool
RegExpPrivateCode::compile(JSContext *cx, JSLinearString &pattern, TokenStream *ts,
uintN *parenCount, RegExpFlag flags)
detail::RegExpPrivateCode::compile(JSContext *cx, JSLinearString &pattern, TokenStream *ts,
uintN *parenCount, RegExpFlag flags)
{
#if ENABLE_YARR_JIT
/* Parse the pattern. */
@ -344,7 +362,7 @@ RegExpPrivateCode::compile(JSContext *cx, JSLinearString &pattern, TokenStream *
}
inline bool
RegExpPrivate::compile(JSContext *cx, TokenStream *ts)
detail::RegExpPrivate::compile(JSContext *cx, TokenStream *ts)
{
if (!sticky())
return code.compile(cx, *source, ts, &parenCount, getFlags());
@ -371,8 +389,8 @@ RegExpPrivate::compile(JSContext *cx, TokenStream *ts)
}
inline RegExpRunStatus
RegExpPrivateCode::execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
int *output, size_t outputCount)
detail::RegExpPrivateCode::execute(JSContext *cx, const jschar *chars, size_t length, size_t start,
int *output, size_t outputCount)
{
int result;
#if ENABLE_YARR_JIT
@ -400,13 +418,13 @@ RegExpPrivateCode::execute(JSContext *cx, const jschar *chars, size_t length, si
}
inline void
RegExpPrivate::incref(JSContext *cx)
detail::RegExpPrivate::incref(JSContext *cx)
{
++refCount;
}
inline void
RegExpPrivate::decref(JSContext *cx)
detail::RegExpPrivate::decref(JSContext *cx)
{
#ifdef JS_THREADSAFE
JS_OPT_ASSERT_IF(cx->runtime->gcHelperThread.getThread(),
@ -423,7 +441,13 @@ RegExpPrivate::decref(JSContext *cx)
cache->remove(ptr);
}
#ifdef DEBUG
this->~RegExpPrivate();
memset(this, 0xcd, sizeof(*this));
cx->free_(this);
#else
cx->delete_(this);
#endif
}
} /* namespace js */

View File

@ -54,6 +54,8 @@ using namespace nanojit;
#endif
using namespace js;
using js::detail::RegExpPrivate;
using js::detail::RegExpPrivateCode;
JS_STATIC_ASSERT(IgnoreCaseFlag == JSREG_FOLD);
JS_STATIC_ASSERT(GlobalFlag == JSREG_GLOB);

View File

@ -65,6 +65,9 @@ enum RegExpRunStatus
class RegExpObject : public ::JSObject
{
typedef detail::RegExpPrivate RegExpPrivate;
typedef detail::RegExpPrivateCode RegExpPrivateCode;
static const uintN LAST_INDEX_SLOT = 0;
static const uintN SOURCE_SLOT = 1;
static const uintN GLOBAL_FLAG_SLOT = 2;
@ -131,6 +134,10 @@ class RegExpObject : public ::JSObject
return RegExpFlag(flags);
}
/* JIT only. */
inline size_t *addressOfPrivateRefCount() const;
/* Flags. */
inline void setIgnoreCase(bool enabled);
@ -147,6 +154,20 @@ class RegExpObject : public ::JSObject
/* Clear out lazy |RegExpPrivate|. */
inline void purge(JSContext *x);
/*
* Trigger an eager creation of the associated RegExpPrivate. Note
* that a GC may purge it away.
*/
bool makePrivateNow(JSContext *cx) {
return !!makePrivate(cx);
}
private:
friend class RegExpObjectBuilder;
friend class RegExpMatcher;
inline bool init(JSContext *cx, JSLinearString *source, RegExpFlag flags);
RegExpPrivate *getOrCreatePrivate(JSContext *cx) {
if (RegExpPrivate *rep = getPrivate())
return rep;
@ -154,11 +175,6 @@ class RegExpObject : public ::JSObject
return makePrivate(cx);
}
private:
friend class RegExpObjectBuilder;
inline bool init(JSContext *cx, JSLinearString *source, RegExpFlag flags);
/* The |RegExpPrivate| is lazily created at the time of use. */
RegExpPrivate *getPrivate() const {
return static_cast<RegExpPrivate *>(JSObject::getPrivate());
@ -189,12 +205,16 @@ class RegExpObject : public ::JSObject
/* Either builds a new RegExpObject or re-initializes an existing one. */
class RegExpObjectBuilder
{
typedef detail::RegExpPrivate RegExpPrivate;
JSContext *cx;
RegExpObject *reobj_;
bool getOrCreate();
bool getOrCreateClone(RegExpObject *proto);
RegExpObject *build(AlreadyIncRefed<RegExpPrivate> rep);
public:
RegExpObjectBuilder(JSContext *cx, RegExpObject *reobj = NULL)
: cx(cx), reobj_(reobj)
@ -202,9 +222,6 @@ class RegExpObjectBuilder
RegExpObject *reobj() { return reobj_; }
/* Note: In case of failure, |rep| will be decrefed. */
RegExpObject *build(AlreadyIncRefed<RegExpPrivate> rep);
RegExpObject *build(JSLinearString *str, RegExpFlag flags);
RegExpObject *build(RegExpObject *other);
@ -212,6 +229,8 @@ class RegExpObjectBuilder
RegExpObject *clone(RegExpObject *other, RegExpObject *proto);
};
namespace detail {
/* Abstracts away the gross |RegExpPrivate| backend details. */
class RegExpPrivateCode
{
@ -356,6 +375,55 @@ class RegExpPrivate
bool sticky() const { return flags & StickyFlag; }
};
} /* namespace detail */
/*
* 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 RegExpPrivate directly.
*
* Note: this exposes precisely the execute interface of a RegExpObject.
*/
class RegExpMatcher
{
typedef detail::RegExpPrivate RegExpPrivate;
JSContext *cx;
AutoRefCount<RegExpPrivate> arc;
public:
explicit RegExpMatcher(JSContext *cx)
: cx(cx), arc(cx)
{ }
bool null() const {
return arc.null();
}
bool global() const {
return arc.get()->global();
}
bool sticky() const {
return arc.get()->sticky();
}
bool reset(RegExpObject *reobj) {
RegExpPrivate *priv = reobj->getOrCreatePrivate(cx);
if (!priv)
return false;
arc.reset(NeedsIncRef<RegExpPrivate>(priv));
return true;
}
inline bool reset(JSLinearString *patstr, JSString *opt);
RegExpRunStatus execute(JSContext *cx, const jschar *chars, size_t length, size_t *lastIndex,
LifoAllocScope &allocScope, MatchPairs **output) {
JS_ASSERT(!arc.null());
return arc.get()->execute(cx, chars, length, lastIndex, allocScope, output);
}
};
/*
* Parse regexp flags. Report an error and return false if an invalid
* sequence of flags is encountered (repeat/invalid flag).
@ -371,6 +439,12 @@ ValueIsRegExp(const Value &v);
inline bool
IsRegExpMetaChar(jschar c);
inline bool
CheckRegExpSyntax(JSContext *cx, JSLinearString *str)
{
return detail::RegExpPrivateCode::checkSyntax(cx, NULL, str);
}
} /* namespace js */
extern JS_FRIEND_API(JSObject *) JS_FASTCALL