Bug 1183400 - Fold and/or expressions. r=efaust
authorJeff Walden <jwalden@mit.edu>
Thu, 28 May 2015 13:47:54 -0700
changeset 288875 9eb62a986338baa03557126320e8debc95a7e74b
parent 288874 5b94aec178f136387abf2da1952b17daff112478
child 288876 c80e97aaf3b79b7c6329d3c2767436a60cfe60a1
push id934
push userraliiev@mozilla.com
push dateMon, 26 Oct 2015 12:58:05 +0000
treeherdermozilla-release@05704e35c1d0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersefaust
bugs1183400
milestone42.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1183400 - Fold and/or expressions. r=efaust
js/src/frontend/FoldConstants.cpp
js/src/tests/ecma_6/Expressions/delete-constant-folded-and-or.js
--- a/js/src/frontend/FoldConstants.cpp
+++ b/js/src/frontend/FoldConstants.cpp
@@ -843,16 +843,99 @@ FoldIncrementDecrement(ExclusiveContext*
     if (!Fold(cx, &target, parser, inGenexpLambda, SyntacticContext::Other))
         return false;
 
     MOZ_ASSERT(parser.isValidSimpleAssignmentTarget(target, Parser<FullParseHandler>::PermitAssignmentToFunctionCalls));
 
     return true;
 }
 
+static bool
+FoldAndOr(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser,
+          bool inGenexpLambda, SyntacticContext sc)
+{
+    ParseNode* node = *nodePtr;
+
+    MOZ_ASSERT(node->isKind(PNK_AND) || node->isKind(PNK_OR));
+    MOZ_ASSERT(node->isArity(PN_LIST));
+
+    bool isOrNode = node->isKind(PNK_OR);
+    ParseNode** elem = &node->pn_head;
+    do {
+        // Pass |sc| through to propagate conditionality.
+        if (!Fold(cx, elem, parser, inGenexpLambda, sc))
+            return false;
+
+        Truthiness t = Boolish(*elem);
+
+        // If we don't know the constant-folded node's truthiness, we can't
+        // reduce this node with its surroundings.  Continue folding any
+        // remaining nodes.
+        if (t == Unknown) {
+            elem = &(*elem)->pn_next;
+            continue;
+        }
+
+        // If the constant-folded node's truthiness will terminate the
+        // condition -- `a || true || expr` or |b && false && expr| -- then
+        // trailing nodes will never be evaluated.  Truncate the list after
+        // the known-truthiness node, as it's the overall result.
+        if ((t == Truthy) == isOrNode) {
+            ParseNode* afterNext;
+            for (ParseNode* next = (*elem)->pn_next; next; next = afterNext) {
+                afterNext = next->pn_next;
+                parser.handler.freeTree(next);
+                --node->pn_count;
+            }
+
+            // Terminate the original and/or list at the known-truthiness
+            // node.
+            (*elem)->pn_next = nullptr;
+            elem = &(*elem)->pn_next;
+            break;
+        }
+
+        MOZ_ASSERT((t == Truthy) == !isOrNode);
+
+        // We've encountered a vacuous node that'll never short- circuit
+        // evaluation.
+        if ((*elem)->pn_next) {
+            // This node is never the overall result when there are
+            // subsequent nodes.  Remove it.
+            ParseNode* elt = *elem;
+            *elem = elt->pn_next;
+            parser.handler.freeTree(elt);
+            --node->pn_count;
+        } else {
+            // Otherwise this node is the result of the overall expression,
+            // so leave it alone.  And we're done.
+            elem = &(*elem)->pn_next;
+            break;
+        }
+    } while (*elem);
+
+    // If the last node in the list was replaced, we need to update the
+    // tail pointer in the original and/or node.
+    node->pn_tail = elem;
+
+    node->checkListConsistency();
+
+    // If we removed nodes, we may have to replace a one-element list with
+    // its element.
+    if (node->pn_count == 1) {
+        ParseNode* first = node->pn_head;
+        ReplaceNode(nodePtr, first);
+
+        node->setKind(PNK_NULL);
+        node->setArity(PN_NULLARY);
+        parser.freeTree(node);
+    }
+
+    return true;
+}
 
 static bool
 FoldConditional(ExclusiveContext* cx, ParseNode** nodePtr, Parser<FullParseHandler>& parser,
                 bool inGenexpLambda)
 {
     ParseNode** nextNode = nodePtr;
 
     do {
@@ -1150,16 +1233,20 @@ Fold(ExclusiveContext* cx, ParseNode** p
         return Fold(cx, &pn->pn_kid, parser, inGenexpLambda, SyntacticContext::Other);
 
       case PNK_SEMI:
         MOZ_ASSERT(pn->isArity(PN_UNARY));
         if (ParseNode*& expr = pn->pn_kid)
             return Fold(cx, &expr, parser, inGenexpLambda, SyntacticContext::Other);
         return true;
 
+      case PNK_AND:
+      case PNK_OR:
+        return FoldAndOr(cx, pnp, parser, inGenexpLambda, sc);
+
       case PNK_EXPORT:
       case PNK_ASSIGN:
       case PNK_ADDASSIGN:
       case PNK_SUBASSIGN:
       case PNK_BITORASSIGN:
       case PNK_BITXORASSIGN:
       case PNK_BITANDASSIGN:
       case PNK_LSHASSIGN:
@@ -1188,18 +1275,16 @@ Fold(ExclusiveContext* cx, ParseNode** p
       case PNK_IMPORT:
       case PNK_EXPORT_FROM:
       case PNK_EXPORT_DEFAULT:
       case PNK_FORIN:
       case PNK_FOROF:
       case PNK_FORHEAD:
       case PNK_CLASS:
       case PNK_TRY:
-      case PNK_OR:
-      case PNK_AND:
       case PNK_BITOR:
       case PNK_BITXOR:
       case PNK_BITAND:
       case PNK_STRICTEQ:
       case PNK_EQ:
       case PNK_STRICTNE:
       case PNK_NE:
       case PNK_LT:
@@ -1265,28 +1350,23 @@ Fold(ExclusiveContext* cx, ParseNode** p
             {
                 return false;
             }
         }
         break;
 
       case PN_LIST:
       {
-        // Propagate Condition context through logical connectives.
-        SyntacticContext kidsc = SyntacticContext::Other;
-        if (pn->isKind(PNK_OR) || pn->isKind(PNK_AND))
-            kidsc = sc;
-
         // Don't fold a parenthesized call expression. See bug 537673.
         ParseNode** listp = &pn->pn_head;
         if ((pn->isKind(PNK_CALL) || pn->isKind(PNK_TAGGED_TEMPLATE)) && (*listp)->isInParens())
             listp = &(*listp)->pn_next;
 
         for (; *listp; listp = &(*listp)->pn_next) {
-            if (!Fold(cx, listp, parser, inGenexpLambda, kidsc))
+            if (!Fold(cx, listp, parser, inGenexpLambda, SyntacticContext::Other))
                 return false;
         }
 
         // If the last node in the list was replaced, pn_tail points into the wrong node.
         pn->pn_tail = listp;
 
         // Save the list head in pn1 for later use.
         pn1 = pn->pn_head;
@@ -1320,36 +1400,25 @@ Fold(ExclusiveContext* cx, ParseNode** p
             if (!Fold(cx, &pn->pn_kid3, parser, inGenexpLambda, SyntacticContext::Other))
                 return false;
         }
         pn3 = pn->pn_kid3;
         break;
 
       case PN_BINARY:
       case PN_BINARY_OBJ:
-        if (pn->isKind(PNK_OR) || pn->isKind(PNK_AND)) {
-            // Propagate Condition context through logical connectives.
-            SyntacticContext kidsc = SyntacticContext::Other;
-            if (sc == SyntacticContext::Condition)
-                kidsc = sc;
-            if (!Fold(cx, &pn->pn_left, parser, inGenexpLambda, kidsc))
-                return false;
-            if (!Fold(cx, &pn->pn_right, parser, inGenexpLambda, kidsc))
+        /* First kid may be null (for default case in switch). */
+        if (pn->pn_left) {
+            if (!Fold(cx, &pn->pn_left, parser, inGenexpLambda, condIf(pn, PNK_WHILE)))
                 return false;
-        } else {
-            /* First kid may be null (for default case in switch). */
-            if (pn->pn_left) {
-                if (!Fold(cx, &pn->pn_left, parser, inGenexpLambda, condIf(pn, PNK_WHILE)))
-                    return false;
-            }
-            /* Second kid may be null (for return in non-generator). */
-            if (pn->pn_right) {
-                if (!Fold(cx, &pn->pn_right, parser, inGenexpLambda, condIf(pn, PNK_DOWHILE)))
-                    return false;
-            }
+        }
+        /* Second kid may be null (for return in non-generator). */
+        if (pn->pn_right) {
+            if (!Fold(cx, &pn->pn_right, parser, inGenexpLambda, condIf(pn, PNK_DOWHILE)))
+                return false;
         }
         pn1 = pn->pn_left;
         pn2 = pn->pn_right;
         break;
 
       case PN_UNARY:
         MOZ_ASSERT(!IsDeleteKind(pn->getKind()),
                    "should have been handled above");
@@ -1386,60 +1455,16 @@ Fold(ExclusiveContext* cx, ParseNode** p
     // the same as |delete (true && x)|. See bug 888002.
     //
     // pn is the immediate child in question. Its descendants were already
     // constant-folded above, so we're done.
     if (sc == SyntacticContext::Delete)
         return true;
 
     switch (pn->getKind()) {
-      case PNK_OR:
-      case PNK_AND:
-        if (sc == SyntacticContext::Condition) {
-            ParseNode** listp = &pn->pn_head;
-            MOZ_ASSERT(*listp == pn1);
-            uint32_t orig = pn->pn_count;
-            do {
-                Truthiness t = Boolish(pn1);
-                if (t == Unknown) {
-                    listp = &pn1->pn_next;
-                    continue;
-                }
-                if ((t == Truthy) == pn->isKind(PNK_OR)) {
-                    for (pn2 = pn1->pn_next; pn2; pn2 = pn3) {
-                        pn3 = pn2->pn_next;
-                        parser.freeTree(pn2);
-                        --pn->pn_count;
-                    }
-                    pn1->pn_next = nullptr;
-                    break;
-                }
-                MOZ_ASSERT((t == Truthy) == pn->isKind(PNK_AND));
-                if (pn->pn_count == 1)
-                    break;
-                *listp = pn1->pn_next;
-                parser.freeTree(pn1);
-                --pn->pn_count;
-            } while ((pn1 = *listp) != nullptr);
-
-            // We may have to replace a one-element list with its element.
-            pn1 = pn->pn_head;
-            if (pn->pn_count == 1) {
-                ReplaceNode(pnp, pn1);
-                pn = pn1;
-            } else if (orig != pn->pn_count) {
-                // Adjust list tail.
-                pn2 = pn1->pn_next;
-                for (; pn1; pn2 = pn1, pn1 = pn1->pn_next)
-                    continue;
-                pn->pn_tail = &pn2->pn_next;
-            }
-        }
-        break;
-
       case PNK_ADD: {
         MOZ_ASSERT(pn->isArity(PN_LIST));
 
         bool folded = false;
 
         pn2 = pn1->pn_next;
         if (pn1->isKind(PNK_NUMBER)) {
             // Fold addition of numeric literals: (1 + 2 + x === 3 + x).
@@ -1575,16 +1600,18 @@ Fold(ExclusiveContext* cx, ParseNode** p
       case PNK_TYPEOFEXPR:
       case PNK_VOID:
       case PNK_NOT:
       case PNK_BITNOT:
       case PNK_POS:
       case PNK_NEG:
       case PNK_CONDITIONAL:
       case PNK_IF:
+      case PNK_AND:
+      case PNK_OR:
         MOZ_CRASH("should have been fully handled above");
 
       case PNK_ELEM: {
         // An indexed expression, pn1[pn2]. A few cases can be improved.
         PropertyName* name = nullptr;
         if (pn2->isKind(PNK_STRING)) {
             JSAtom* atom = pn2->pn_atom;
             uint32_t index;
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_6/Expressions/delete-constant-folded-and-or.js
@@ -0,0 +1,41 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/licenses/publicdomain/
+
+//-----------------------------------------------------------------------------
+var BUGNUMBER = 1183400;
+var summary =
+  "Deletion of a && or || expression that constant-folds to a name must not " +
+  "attempt to delete the name";
+
+print(BUGNUMBER + ": " + summary);
+
+/**************
+ * BEGIN TEST *
+ **************/
+
+Object.defineProperty(this, "nonconfigurable", { value: 42 });
+assertEq(nonconfigurable, 42);
+
+assertEq(delete nonconfigurable, false);
+assertEq(delete (true && nonconfigurable), true);
+
+function nested()
+{
+  assertEq(delete nonconfigurable, false);
+  assertEq(delete (true && nonconfigurable), true);
+}
+nested();
+
+function nestedStrict()
+{
+  "use strict";
+  assertEq(delete (true && nonconfigurable), true);
+}
+nestedStrict();
+
+/******************************************************************************/
+
+if (typeof reportCompare === "function")
+  reportCompare(true, true);
+
+print("Tests complete");