diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index 6a5172e477b..092ca75d85f 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -5524,8 +5524,19 @@ JS_SetRegExpInput(JSContext *cx, JSString *input, JSBool multiline) JS_PUBLIC_API(void) JS_ClearRegExpStatics(JSContext *cx) { + JSRegExpStatics *res; + /* No locking required, cx is thread-private and input must be live. */ - cx->regExpStatics.clear(); + res = &cx->regExpStatics; + res->input = NULL; + res->multiline = JS_FALSE; + res->parenCount = 0; + res->lastMatch = res->lastParen = js_EmptySubString; + res->leftContext = res->rightContext = js_EmptySubString; + if (res->moreParens) { + cx->free(res->moreParens); + res->moreParens = NULL; + } cx->runtime->gcPoke = JS_TRUE; } diff --git a/js/src/jscntxt.h b/js/src/jscntxt.h index 3b678262f52..41b6a2d576e 100644 --- a/js/src/jscntxt.h +++ b/js/src/jscntxt.h @@ -1173,42 +1173,9 @@ namespace js { class AutoGCRooter; } -struct JSRegExpStatics { - JSString *input; /* input string to match (perl $_, GC root) */ - JSBool multiline; /* whether input contains newlines (perl $*) */ - JSSubString lastMatch; /* last string matched (perl $&) */ - JSSubString lastParen; /* last paren matched (perl $+) */ - JSSubString leftContext; /* input to left of last match (perl $`) */ - JSSubString rightContext; /* input to right of last match (perl $') */ - js::Vector parens; /* last set of parens matched (perl $1, $2) */ - - JSRegExpStatics(JSContext *cx) : parens(cx) {} - - bool copy(const JSRegExpStatics& other) { - input = other.input; - multiline = other.multiline; - lastMatch = other.lastMatch; - lastParen = other.lastParen; - leftContext = other.leftContext; - rightContext = other.rightContext; - if (!parens.resize(other.parens.length())) - return false; - memcpy(parens.begin(), other.parens.begin(), sizeof(JSSubString) * parens.length()); - return true; - } - - void clear() { - input = NULL; - multiline = false; - lastMatch = lastParen = leftContext = rightContext = js_EmptySubString; - parens.clear(); - } -}; - struct JSContext { - explicit JSContext(JSRuntime *rt) : - runtime(rt), regExpStatics(this), busyArrays(this) {} + explicit JSContext(JSRuntime *rt) : runtime(rt), busyArrays(this) {} /* * If this flag is set, we were asked to call back the operation callback @@ -1294,7 +1261,7 @@ struct JSContext /* Storage to root recently allocated GC things and script result. */ JSWeakRoots weakRoots; - /* Regular expression class statics. */ + /* Regular expression class statics (XXX not shared globally). */ JSRegExpStatics regExpStatics; /* State for object and array toSource conversion. */ diff --git a/js/src/jsregexp.cpp b/js/src/jsregexp.cpp index 1b40021f65e..eaf086c3854 100644 --- a/js/src/jsregexp.cpp +++ b/js/src/jsregexp.cpp @@ -4865,10 +4865,11 @@ js_ExecuteRegExp(JSContext *cx, JSRegExp *re, JSString *str, size_t *indexp, const jschar *cp, *ep; size_t i, length, start; + JSSubString *morepar; JSBool ok; JSRegExpStatics *res; ptrdiff_t matchlen; - uintN num; + uintN num, morenum; JSString *parstr, *matchstr; JSObject *obj; @@ -4972,22 +4973,45 @@ js_ExecuteRegExp(JSContext *cx, JSRegExp *re, JSString *str, size_t *indexp, res = &cx->regExpStatics; res->input = str; - if (!res->parens.resize(re->parenCount)) { - ok = JS_FALSE; - goto out; - } + res->parenCount = uint16(re->parenCount); if (re->parenCount == 0) { res->lastParen = js_EmptySubString; } else { for (num = 0; num < re->parenCount; num++) { - JSSubString *sub = &res->parens[num]; parsub = &result->parens[num]; - if (parsub->index == -1) { - sub->chars = NULL; - sub->length = 0; + if (num < 9) { + if (parsub->index == -1) { + res->parens[num].chars = NULL; + res->parens[num].length = 0; + } else { + res->parens[num].chars = gData.cpbegin + parsub->index; + res->parens[num].length = parsub->length; + } } else { - sub->chars = gData.cpbegin + parsub->index; - sub->length = parsub->length; + morenum = num - 9; + morepar = res->moreParens; + if (!morepar) { + res->moreLength = 10; + morepar = (JSSubString*) + cx->malloc(10 * sizeof(JSSubString)); + } else if (morenum >= res->moreLength) { + res->moreLength += 10; + morepar = (JSSubString*) + cx->realloc(morepar, + res->moreLength * sizeof(JSSubString)); + } + if (!morepar) { + ok = JS_FALSE; + goto out; + } + res->moreParens = morepar; + if (parsub->index == -1) { + morepar[morenum].chars = NULL; + morepar[morenum].length = 0; + } else { + morepar[morenum].chars = gData.cpbegin + parsub->index; + morepar[morenum].length = parsub->length; + } } if (test) continue; @@ -5185,9 +5209,14 @@ JS_FRIEND_API(void) js_SaveAndClearRegExpStatics(JSContext *cx, JSRegExpStatics *statics, AutoValueRooter *tvr) { - statics->copy(cx->regExpStatics); + *statics = cx->regExpStatics; if (statics->input) tvr->setString(statics->input); + /* + * Prevent JS_ClearRegExpStatics from freeing moreParens, since we've only + * moved it elsewhere (into statics->moreParens). + */ + cx->regExpStatics.moreParens = NULL; JS_ClearRegExpStatics(cx); } @@ -5196,7 +5225,8 @@ js_RestoreRegExpStatics(JSContext *cx, JSRegExpStatics *statics, AutoValueRooter *tvr) { /* Clear/free any new JSRegExpStatics data before clobbering. */ - cx->regExpStatics.copy(*statics); + JS_ClearRegExpStatics(cx); + cx->regExpStatics = *statics; } void @@ -5248,7 +5278,7 @@ regexp_static_getProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) sub = &res->rightContext; break; default: - sub = (size_t(slot) < res->parens.length()) ? &res->parens[slot] : &js_EmptySubString; + sub = REGEXP_PAREN_SUBSTRING(res, slot); break; } str = js_NewStringCopyN(cx, sub->chars, sub->length); diff --git a/js/src/jsregexp.h b/js/src/jsregexp.h index cbec37e875a..c8ba7232ec9 100644 --- a/js/src/jsregexp.h +++ b/js/src/jsregexp.h @@ -52,6 +52,19 @@ JS_BEGIN_EXTERN_C +struct JSRegExpStatics { + JSString *input; /* input string to match (perl $_, GC root) */ + JSBool multiline; /* whether input contains newlines (perl $*) */ + uint16 parenCount; /* number of valid elements in parens[] */ + uint16 moreLength; /* number of allocated elements in moreParens */ + JSSubString parens[9]; /* last set of parens matched (perl $1, $2) */ + JSSubString *moreParens; /* null or realloc'd vector for $10, etc. */ + JSSubString lastMatch; /* last string matched (perl $&) */ + JSSubString lastParen; /* last paren matched (perl $+) */ + JSSubString leftContext; /* input to left of last match (perl $`) */ + JSSubString rightContext; /* input to right of last match (perl $') */ +}; + namespace js { class AutoValueRooter; } extern JS_FRIEND_API(void) @@ -83,6 +96,17 @@ typedef struct RECharSet { } u; } RECharSet; +/* + * This macro is safe because moreParens is guaranteed to be allocated and big + * enough to hold parenCount, or else be null when parenCount is 0. + */ +#define REGEXP_PAREN_SUBSTRING(res, num) \ + (((jsuint)(num) < (jsuint)(res)->parenCount) \ + ? ((jsuint)(num) < 9) \ + ? &(res)->parens[num] \ + : &(res)->moreParens[(num) - 9] \ + : &js_EmptySubString) + typedef struct RENode RENode; struct JSRegExp { diff --git a/js/src/jsstr.cpp b/js/src/jsstr.cpp index c1008911ed7..0f8424300bc 100644 --- a/js/src/jsstr.cpp +++ b/js/src/jsstr.cpp @@ -1719,13 +1719,13 @@ InterpretDollar(JSContext *cx, jschar *dp, jschar *ep, ReplaceData &rdata, if (JS7_ISDEC(dc)) { /* ECMA-262 Edition 3: 1-9 or 01-99 */ num = JS7_UNDEC(dc); - if (num > res->parens.length()) + if (num > res->parenCount) return NULL; cp = dp + 2; if (cp < ep && (dc = *cp, JS7_ISDEC(dc))) { tmp = 10 * num + JS7_UNDEC(dc); - if (tmp <= res->parens.length()) { + if (tmp <= res->parenCount) { cp++; num = tmp; } @@ -1736,7 +1736,7 @@ InterpretDollar(JSContext *cx, jschar *dp, jschar *ep, ReplaceData &rdata, /* Adjust num from 1 $n-origin to 0 array-index-origin. */ num--; *skip = cp - dp; - return (num < res->parens.length()) ? &res->parens[num] : &js_EmptySubString; + return REGEXP_PAREN_SUBSTRING(res, num); } *skip = 2; @@ -1780,6 +1780,8 @@ FindReplaceLength(JSContext *cx, ReplaceData &rdata, size_t *sizep) lambda = rdata.lambda; if (lambda) { + uintN i, m, n; + LeaveTrace(cx); /* @@ -1800,6 +1802,18 @@ FindReplaceLength(JSContext *cx, ReplaceData &rdata, size_t *sizep) } jsval* invokevp = rdata.invokevp; + MUST_FLOW_THROUGH("lambda_out"); + bool ok = false; + bool freeMoreParens = false; + + /* + * Save the regExpStatics from the current regexp, since they may be + * clobbered by a RegExp usage in the lambda function. Note that all + * members of JSRegExpStatics are JSSubStrings, so not GC roots, save + * input, which is rooted otherwise via vp[1] in str_replace. + */ + JSRegExpStatics save = cx->regExpStatics; + /* Push lambda and its 'this' parameter. */ jsval *sp = invokevp; *sp++ = OBJECT_TO_JSVAL(lambda); @@ -1807,13 +1821,27 @@ FindReplaceLength(JSContext *cx, ReplaceData &rdata, size_t *sizep) /* Push $&, $1, $2, ... */ if (!PushRegExpSubstr(cx, cx->regExpStatics.lastMatch, sp)) - return false; + goto lambda_out; - uintN i = 0; - for (uintN n = cx->regExpStatics.parens.length(); i < n; i++) { - if (!PushRegExpSubstr(cx, cx->regExpStatics.parens[i], sp)) - return false; + i = 0; + m = cx->regExpStatics.parenCount; + n = JS_MIN(m, 9); + for (uintN j = 0; i < n; i++, j++) { + if (!PushRegExpSubstr(cx, cx->regExpStatics.parens[j], sp)) + goto lambda_out; } + for (uintN j = 0; i < m; i++, j++) { + if (!PushRegExpSubstr(cx, cx->regExpStatics.moreParens[j], sp)) + goto lambda_out; + } + + /* + * We need to clear moreParens in the top-of-stack cx->regExpStatics + * so it won't be possibly realloc'ed, leaving the bottom-of-stack + * moreParens pointing to freed memory. + */ + cx->regExpStatics.moreParens = NULL; + freeMoreParens = true; /* Make sure to push undefined for any unmatched parens. */ for (; i < p; i++) @@ -1824,7 +1852,7 @@ FindReplaceLength(JSContext *cx, ReplaceData &rdata, size_t *sizep) *sp++ = STRING_TO_JSVAL(rdata.str); if (!js_Invoke(cx, argc, invokevp, 0)) - return false; + goto lambda_out; /* * NB: we count on the newborn string root to hold any string @@ -1833,12 +1861,18 @@ FindReplaceLength(JSContext *cx, ReplaceData &rdata, size_t *sizep) */ repstr = js_ValueToString(cx, *invokevp); if (!repstr) - return false; + goto lambda_out; rdata.repstr = repstr; *sizep = repstr->length(); - return true; + ok = true; + + lambda_out: + if (freeMoreParens) + cx->free(cx->regExpStatics.moreParens); + cx->regExpStatics = save; + return ok; } repstr = rdata.repstr; @@ -2178,11 +2212,10 @@ str_split(JSContext *cx, uintN argc, jsval *vp) * substring that was delimited. */ if (re && sep->chars) { - JSRegExpStatics *res = &cx->regExpStatics; - for (uintN num = 0; num < res->parens.length(); num++) { + for (uintN num = 0; num < cx->regExpStatics.parenCount; num++) { if (limited && len >= limit) break; - JSSubString *parsub = &res->parens[num]; + JSSubString *parsub = REGEXP_PAREN_SUBSTRING(&cx->regExpStatics, num); sub = js_NewStringCopyN(cx, parsub->chars, parsub->length); if (!sub || !splits.push(sub)) return false; diff --git a/js/src/xpconnect/src/XPCSafeJSObjectWrapper.cpp b/js/src/xpconnect/src/XPCSafeJSObjectWrapper.cpp index 447b94ddecd..3926f146e29 100644 --- a/js/src/xpconnect/src/XPCSafeJSObjectWrapper.cpp +++ b/js/src/xpconnect/src/XPCSafeJSObjectWrapper.cpp @@ -534,7 +534,7 @@ XPC_SJOW_DelProperty(JSContext *cx, JSObject *obj, jsval id, jsval *vp) NS_STACK_CLASS class SafeCallGuard { public: SafeCallGuard(JSContext *cx, nsIPrincipal *principal) - : cx(cx), statics(cx), tvr(cx) { + : cx(cx), tvr(cx) { nsIScriptSecurityManager *ssm = XPCWrapper::GetSecurityManager(); if (ssm) { // Note: We pass null as the target frame pointer because we know that