Bug 605754 - regexp topcrash diagnostics. r=dmandelin, a=dmandelin
authorChris Leary <cdleary@mozilla.com>
Mon, 15 Nov 2010 19:40:26 -0800
changeset 57585 4a224009eab510b719fa68ec5a7f56e6f3b55220
parent 57584 f0458767cf4b2d8f37eabb8feb9475fbccf43f18
child 57586 9fb566e5fa3a5d75789c988f083221a86274f429
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewersdmandelin, dmandelin
bugs605754
milestone2.0b8pre
Bug 605754 - regexp topcrash diagnostics. r=dmandelin, a=dmandelin
js/src/jscntxt.h
js/src/jsregexp.h
js/src/jsregexpinlines.h
js/src/jsstr.cpp
js/src/jsutil.h
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -2320,29 +2320,39 @@ struct JSContext
     void assertValidStackDepth(uintN depth) {
         JS_ASSERT(0 <= regs->sp - regs->fp->base());
         JS_ASSERT(depth <= uintptr_t(regs->sp - regs->fp->base()));
     }
 #else
     void assertValidStackDepth(uintN /*depth*/) {}
 #endif
 
+    enum DollarPath {
+        DOLLAR_LITERAL = 1,
+        DOLLAR_AMP,
+        DOLLAR_PLUS,
+        DOLLAR_TICK,
+        DOLLAR_QUOT
+    };
+    volatile DollarPath *dollarPath;
+    volatile jschar *blackBox;
+
 private:
 
     /*
      * The allocation code calls the function to indicate either OOM failure
      * when p is null or that a memory pressure counter has reached some
      * threshold when p is not null. The function takes the pointer and not
      * a boolean flag to minimize the amount of code in its inlined callers.
      */
     JS_FRIEND_API(void) checkMallocGCPressure(void *p);
 
     /* To silence MSVC warning about using 'this' in a member initializer. */
     JSContext *thisInInitializer() { return this; }
-};
+}; /* struct JSContext */
 
 #ifdef JS_THREADSAFE
 # define JS_THREAD_ID(cx)       ((cx)->thread ? (cx)->thread->id : 0)
 #endif
 
 #if defined JS_THREADSAFE && defined DEBUG
 
 namespace js {
--- a/js/src/jsregexp.h
+++ b/js/src/jsregexp.h
@@ -70,16 +70,21 @@ class RegExpStatics
 
     bool createDependent(JSContext *cx, size_t start, size_t end, Value *out) const;
 
     size_t pairCount() const {
         JS_ASSERT(matchPairs.length() % 2 == 0);
         return matchPairs.length() / 2;
     }
 
+    size_t pairCountCrash() const {
+        JS_CRASH_UNLESS(matchPairs.length() % 2 == 0);
+        return pairCount();
+    }
+
     void copyTo(RegExpStatics &dst) {
         dst.matchPairs.clear();
         /* 'save' has already reserved space in matchPairs */
         JS_ALWAYS_TRUE(dst.matchPairs.append(matchPairs));
         dst.matchPairsInput = matchPairsInput;
         dst.pendingInput = pendingInput;
         dst.flags = flags;
     }
@@ -132,16 +137,21 @@ class RegExpStatics
 #endif
     }
 
     int get(size_t pairNum, bool which) const {
         JS_ASSERT(pairNum < pairCount());
         return matchPairs[2 * pairNum + which];
     }
 
+    int getCrash(size_t pairNum, bool which) const {
+        JS_CRASH_UNLESS(pairNum < pairCountCrash());
+        return get(pairNum, which);
+    }
+
     /*
      * Check whether the index at |checkValidIndex| is valid (>= 0).
      * If so, construct a string for it and place it in |*out|.
      * If not, place undefined in |*out|.
      */
     bool makeMatch(JSContext *cx, size_t checkValidIndex, size_t pairNum, Value *out) const;
 
     static const uintN allFlags = JSREG_FOLD | JSREG_GLOB | JSREG_STICKY | JSREG_MULTILINE;
@@ -241,19 +251,19 @@ class RegExpStatics
     void mark(JSTracer *trc) const {
         if (pendingInput)
             JS_CALL_STRING_TRACER(trc, pendingInput, "res->pendingInput");
         if (matchPairsInput)
             JS_CALL_STRING_TRACER(trc, matchPairsInput, "res->matchPairsInput");
     }
 
     size_t getParenLength(size_t parenNum) const {
-        if (pairCount() <= parenNum + 1)
+        if (pairCountCrash() <= parenNum + 1)
             return 0;
-        return get(parenNum + 1, 1) - get(parenNum + 1, 0);
+        return getCrash(parenNum + 1, 1) - getCrash(parenNum + 1, 0);
     }
 
     /* Value creators. */
 
     bool createPendingInput(JSContext *cx, Value *out) const;
     bool createLastMatch(JSContext *cx, Value *out) const { return makeMatch(cx, 0, 0, out); }
     bool createLastParen(JSContext *cx, Value *out) const;
     bool createLeftContext(JSContext *cx, Value *out) const;
--- a/js/src/jsregexpinlines.h
+++ b/js/src/jsregexpinlines.h
@@ -81,17 +81,17 @@ class RegExp
     JSC::Yarr::RegexCodeBlock   compiled;
 #else
     JSRegExp                    *compiled;
 #endif
     unsigned                    parenCount;
     uint32                      flags;
 
     RegExp(JSString *source, uint32 flags)
-      : refCount(1), source(source), compiled(), parenCount(), flags(flags) {}
+      : refCount(1), source(source), compiled(), parenCount(0), flags(flags) {}
     bool compileHelper(JSContext *cx, UString &pattern);
     bool compile(JSContext *cx);
     static const uint32 allFlags = JSREG_FOLD | JSREG_GLOB | JSREG_MULTILINE | JSREG_STICKY;
     void handlePCREError(JSContext *cx, int error);
     void handleYarrError(JSContext *cx, int error);
     static inline bool initArena(JSContext *cx);
     static inline void checkMatchPairs(JSString *input, int *buf, size_t matchItemCount);
     static JSObject *createResult(JSContext *cx, JSString *input, int *buf, size_t matchItemCount);
@@ -155,16 +155,17 @@ class RegExp
 
     /* Accessors. */
     JSString *getSource() const { return source; }
     size_t getParenCount() const { return parenCount; }
     bool ignoreCase() const { return flags & JSREG_FOLD; }
     bool global() const { return flags & JSREG_GLOB; }
     bool multiline() const { return flags & JSREG_MULTILINE; }
     bool sticky() const { return flags & JSREG_STICKY; }
+
     const uint32 &getFlags() const { JS_ASSERT((flags & allFlags) == flags); return flags; }
     uint32 flagCount() const;
 };
 
 class RegExpMatchBuilder
 {
     JSContext   * const cx;
     JSObject    * const array;
@@ -615,64 +616,64 @@ RegExpStatics::createRightContext(JSCont
         return true;
     }
     return createDependent(cx, matchPairs[1], matchPairsInput->length(), out);
 }
 
 inline void
 RegExpStatics::getParen(size_t num, JSSubString *out) const
 {
-    out->chars = matchPairsInput->chars() + get(num + 1, 0);
+    out->chars = matchPairsInput->chars() + getCrash(num + 1, 0);
     out->length = getParenLength(num);
 }
 
 inline void
 RegExpStatics::getLastMatch(JSSubString *out) const
 {
-    if (!pairCount()) {
+    if (!pairCountCrash()) {
         *out = js_EmptySubString;
         return;
     }
-    JS_ASSERT(matchPairsInput);
-    out->chars = matchPairsInput->chars() + get(0, 0);
-    JS_ASSERT(get(0, 1) >= get(0, 0));
+    JS_CRASH_UNLESS(matchPairsInput);
+    out->chars = matchPairsInput->chars() + getCrash(0, 0);
+    JS_CRASH_UNLESS(getCrash(0, 1) >= getCrash(0, 0));
     out->length = get(0, 1) - get(0, 0);
 }
 
 inline void
 RegExpStatics::getLastParen(JSSubString *out) const
 {
-    if (!pairCount()) {
+    if (!pairCountCrash()) {
         *out = js_EmptySubString;
         return;
     }
     size_t num = pairCount() - 1;
-    out->chars = matchPairsInput->chars() + get(num, 0);
-    JS_ASSERT(get(num, 1) >= get(num, 0));
+    out->chars = matchPairsInput->chars() + getCrash(num, 0);
+    JS_CRASH_UNLESS(getCrash(num, 1) >= get(num, 0));
     out->length = get(num, 1) - get(num, 0);
 }
 
 inline void
 RegExpStatics::getLeftContext(JSSubString *out) const
 {
-    if (!pairCount()) {
+    if (!pairCountCrash()) {
         *out = js_EmptySubString;
         return;
     }
     out->chars = matchPairsInput->chars();
-    out->length = get(0, 0);
+    out->length = getCrash(0, 0);
 }
 
 inline void
 RegExpStatics::getRightContext(JSSubString *out) const
 {
-    if (!pairCount()) {
+    if (!pairCountCrash()) {
         *out = js_EmptySubString;
         return;
     }
-    out->chars = matchPairsInput->chars() + get(0, 1);
-    JS_ASSERT(get(0, 1) <= int(matchPairsInput->length()));
+    out->chars = matchPairsInput->chars() + getCrash(0, 1);
+    JS_CRASH_UNLESS(get(0, 1) <= int(matchPairsInput->length()));
     out->length = matchPairsInput->length() - get(0, 1);
 }
 
 }
 
 #endif /* jsregexpinlines_h___ */
--- a/js/src/jsstr.cpp
+++ b/js/src/jsstr.cpp
@@ -1980,17 +1980,17 @@ struct ReplaceData
     bool               calledBack;     /* record whether callback has been called */
     InvokeSessionGuard session;        /* arguments for repeated lambda Invoke call */
     InvokeArgsGuard    singleShot;     /* arguments for single lambda Invoke call */
     JSCharBuffer       cb;             /* buffer built during DoMatch */
 };
 
 static bool
 InterpretDollar(JSContext *cx, RegExpStatics *res, jschar *dp, jschar *ep, ReplaceData &rdata,
-                JSSubString *out, size_t *skip)
+                JSSubString *out, size_t *skip, volatile JSContext::DollarPath *path)
 {
     JS_ASSERT(*dp == '$');
 
     /* If there is only a dollar, bail now */
     if (dp + 1 >= ep)
         return false;
 
     /* Interpret all Perl match-induced dollar variables. */
@@ -2023,28 +2023,33 @@ InterpretDollar(JSContext *cx, RegExpSta
     }
 
     *skip = 2;
     switch (dc) {
       case '$':
         rdata.dollarStr.chars = dp;
         rdata.dollarStr.length = 1;
         *out = rdata.dollarStr;
+        *path = JSContext::DOLLAR_LITERAL;
         return true;
       case '&':
         res->getLastMatch(out);
+        *path = JSContext::DOLLAR_AMP;
         return true;
       case '+':
         res->getLastParen(out);
+        *path = JSContext::DOLLAR_PLUS;
         return true;
       case '`':
         res->getLeftContext(out);
+        *path = JSContext::DOLLAR_TICK;
         return true;
       case '\'':
         res->getRightContext(out);
+        *path = JSContext::DOLLAR_QUOT;
         return true;
     }
     return false;
 }
 
 static bool
 FindReplaceLength(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, size_t *sizep)
 {
@@ -2147,45 +2152,57 @@ FindReplaceLength(JSContext *cx, RegExpS
             return false;
 
         *sizep = rdata.repstr->length();
         return true;
     }
 
     JSString *repstr = rdata.repstr;
     size_t replen = repstr->length();
+    JSContext::DollarPath path;
     for (jschar *dp = rdata.dollar, *ep = rdata.dollarEnd; dp; dp = js_strchr_limit(dp, '$', ep)) {
         JSSubString sub;
         size_t skip;
-        if (InterpretDollar(cx, res, dp, ep, rdata, &sub, &skip)) {
+        if (InterpretDollar(cx, res, dp, ep, rdata, &sub, &skip, &path)) {
             replen += sub.length - skip;
             dp += skip;
         } else {
             dp++;
         }
     }
     *sizep = replen;
     return true;
 }
 
 static void
 DoReplace(JSContext *cx, RegExpStatics *res, ReplaceData &rdata, jschar *chars)
 {
     JSString *repstr = rdata.repstr;
     jschar *cp;
     jschar *bp = cp = repstr->chars();
+    volatile JSContext::DollarPath path;
+    cx->dollarPath = &path;
+    jschar sourceBuf[128];
+    cx->blackBox = sourceBuf;
+
     for (jschar *dp = rdata.dollar, *ep = rdata.dollarEnd; dp; dp = js_strchr_limit(dp, '$', ep)) {
         size_t len = dp - cp;
         js_strncpy(chars, cp, len);
         chars += len;
         cp = dp;
 
         JSSubString sub;
         size_t skip;
-        if (InterpretDollar(cx, res, dp, ep, rdata, &sub, &skip)) {
+        if (InterpretDollar(cx, res, dp, ep, rdata, &sub, &skip, &path)) {
+            if (((size_t(sub.chars) & 0xfffffU) + sub.length) > 0x100000U) {
+                /* Going to cross a 0xffffe address, so take a gander at the replace value. */
+                size_t peekLen = JS_MIN(rdata.dollarEnd - rdata.dollar, 128);
+                js_strncpy(sourceBuf, rdata.dollar, peekLen);
+            }
+
             len = sub.length;
             js_strncpy(chars, sub.chars, len);
             chars += len;
             cp += skip;
             dp += skip;
         } else {
             dp++;
         }
--- a/js/src/jsutil.h
+++ b/js/src/jsutil.h
@@ -51,16 +51,24 @@ JS_BEGIN_EXTERN_C
 
 /*
  * JS_Assert is present even in release builds, for the benefit of applications
  * that build DEBUG and link against a non-DEBUG SpiderMonkey library.
  */
 extern JS_PUBLIC_API(void)
 JS_Assert(const char *s, const char *file, JSIntn ln);
 
+#define JS_CRASH_UNLESS(__cond)                                                 \
+    JS_BEGIN_MACRO                                                              \
+        if (!(__cond)) {                                                        \
+            *(int *)(uintptr_t)0xccadbeef = 0;                                  \
+            ((void(*)())0)(); /* More reliable, but doesn't say CCADBEEF */     \
+        }                                                                       \
+    JS_END_MACRO
+
 #ifdef DEBUG
 
 #define JS_ASSERT(expr)                                                       \
     ((expr) ? (void)0 : JS_Assert(#expr, __FILE__, __LINE__))
 
 #define JS_ASSERT_IF(cond, expr)                                              \
     ((!(cond) || (expr)) ? (void)0 : JS_Assert(#expr, __FILE__, __LINE__))