Bug 972961 - Fix a stack overflow in FoldConstants.cpp. r=luke.
authorJason Orendorff <jorendorff@mozilla.com>
Mon, 10 Mar 2014 16:28:44 -0500
changeset 172855 236e257bf505324d68fe3552677c981672001848
parent 172854 02400c717fa61b09b91b5f08326d50404eaad3b8
child 172856 6635d1edc7497a5fa346d0921013d0fde248c0bb
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersluke
bugs972961
milestone30.0a1
Bug 972961 - Fix a stack overflow in FoldConstants.cpp. r=luke.
js/src/frontend/FoldConstants.cpp
js/src/jit-test/tests/basic/bug972961.js
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -23,56 +23,78 @@ using namespace js;
 using namespace js::frontend;
 
 using mozilla::IsNaN;
 using mozilla::IsNegative;
 using mozilla::NegativeInfinity;
 using mozilla::PositiveInfinity;
 using JS::GenericNaN;
 
-static ParseNode *
-ContainsVarOrConst(ParseNode *pn)
+static bool
+ContainsVarOrConst(ExclusiveContext *cx, ParseNode *pn, ParseNode **resultp)
 {
-    if (!pn)
-        return nullptr;
-    if (pn->isKind(PNK_VAR) || pn->isKind(PNK_CONST))
-        return pn;
+    JS_CHECK_RECURSION(cx, return false);
+
+    if (!pn) {
+        *resultp = nullptr;
+        return true;
+    }
+    if (pn->isKind(PNK_VAR) || pn->isKind(PNK_CONST)) {
+        *resultp = pn;
+        return true;
+    }
     switch (pn->getArity()) {
       case PN_LIST:
         for (ParseNode *pn2 = pn->pn_head; pn2; pn2 = pn2->pn_next) {
-            if (ParseNode *pnt = ContainsVarOrConst(pn2))
-                return pnt;
+            if (!ContainsVarOrConst(cx, pn2, resultp))
+                return false;
+            if (*resultp)
+                return true;
         }
         break;
+
       case PN_TERNARY:
-        if (ParseNode *pnt = ContainsVarOrConst(pn->pn_kid1))
-            return pnt;
-        if (ParseNode *pnt = ContainsVarOrConst(pn->pn_kid2))
-            return pnt;
-        return ContainsVarOrConst(pn->pn_kid3);
+        if (!ContainsVarOrConst(cx, pn->pn_kid1, resultp))
+            return false;
+        if (*resultp)
+            return true;
+        if (!ContainsVarOrConst(cx, pn->pn_kid2, resultp))
+            return false;
+        if (*resultp)
+            return true;
+        return ContainsVarOrConst(cx, pn->pn_kid3, resultp);
+
       case PN_BINARY:
       case PN_BINARY_OBJ:
-        /*
-         * Limit recursion if pn is a binary expression, which can't contain a
-         * var statement.
-         */
-        if (!pn->isOp(JSOP_NOP))
-            return nullptr;
-        if (ParseNode *pnt = ContainsVarOrConst(pn->pn_left))
-            return pnt;
-        return ContainsVarOrConst(pn->pn_right);
+        // Limit recursion if pn is a binary expression, which can't contain a
+        // var statement.
+        if (!pn->isOp(JSOP_NOP)) {
+            *resultp = nullptr;
+            return true;
+        }
+        if (!ContainsVarOrConst(cx, pn->pn_left, resultp))
+            return false;
+        if (*resultp)
+            return true;
+        return ContainsVarOrConst(cx, pn->pn_right, resultp);
+
       case PN_UNARY:
-        if (!pn->isOp(JSOP_NOP))
-            return nullptr;
-        return ContainsVarOrConst(pn->pn_kid);
+        if (!pn->isOp(JSOP_NOP)) {
+            *resultp = nullptr;
+            return true;
+        }
+        return ContainsVarOrConst(cx, pn->pn_kid, resultp);
+
       case PN_NAME:
-        return ContainsVarOrConst(pn->maybeExpr());
+        return ContainsVarOrConst(cx, pn->maybeExpr(), resultp);
+
       default:;
     }
-    return nullptr;
+    *resultp = nullptr;
+    return true;
 }
 
 /*
  * Fold from one constant type to another.
  * XXX handles only strings and numbers for now
  */
 static bool
 FoldType(ExclusiveContext *cx, ParseNode *pn, ParseNodeKind kind)
@@ -404,18 +426,27 @@ Fold(ExclusiveContext *cx, ParseNode **p
     //
     // pn is the immediate child in question. Its descendents were already
     // constant-folded above, so we're done.
     if (sc == SyntacticContext::Delete)
         return true;
 
     switch (pn->getKind()) {
       case PNK_IF:
-        if (ContainsVarOrConst(pn2) || ContainsVarOrConst(pn3))
-            break;
+        {
+            ParseNode *decl;
+            if (!ContainsVarOrConst(cx, pn2, &decl))
+                return false;
+            if (decl)
+                break;
+            if (!ContainsVarOrConst(cx, pn3, &decl))
+                return false;
+            if (decl)
+                break;
+        }
         /* FALL THROUGH */
 
       case PNK_CONDITIONAL:
         /* Reduce 'if (C) T; else E' into T for true C, E for false. */
         switch (pn1->getKind()) {
           case PNK_NUMBER:
             if (pn1->pn_dval == 0 || IsNaN(pn1->pn_dval))
                 pn2 = pn3;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/bug972961.js
@@ -0,0 +1,40 @@
+// Bug 972961 - Test compiler with very long chains of property accesses
+
+// Return true if we can compile a chain of n property accesses,
+// false if we cannot. Throw if compilation fails in an unexpected way.
+function test(n) {
+    print("testing " + n);
+    try {
+        eval('if (false) {' + Array(n).join(("a.")) + 'a}');
+    } catch (exc) {
+        // Expected outcome if the expression is too deeply nested is an InternalError.
+        if (!(exc instanceof InternalError))
+            throw exc;
+        print(exc.message);
+        return false;
+    }
+    print("no exception");
+    return true;
+}
+
+// Find out how long a chain is enough to break the compiler.
+var n = 4, LIMIT = 0x000fffff;
+var lo = 1, hi = 1;
+while (n <= LIMIT && test(n)) {
+    lo = n;
+    n *= 4;
+}
+
+// Using binary search, find a pass/fail boundary (in order to
+// test the edge case).
+if (n <= LIMIT) {
+    hi = n;
+    while (lo !== hi) {
+        var mid = Math.floor((lo + hi) / 2);
+        if (test(mid))
+            lo = mid + 1;
+        else
+            hi = mid;
+    }
+    print((lo - 1) + " attributes should be enough for anyone");
+}