Sfoglia il codice sorgente

Fixes Bug#10330888 and duplicates. Fix Block ID accounting. Do not eat left parentheses when parsing destructured var declarations.

Atul Katti 9 anni fa
parent
commit
5dd7568ea7

+ 49 - 13
lib/Parser/Parse.cpp

@@ -1920,6 +1920,7 @@ void Parser::CaptureState(ParserState *state)
     state->m_ppnodeScopeSave = m_ppnodeScope;
     state->m_ppnodeExprScopeSave = m_ppnodeExprScope;
     state->m_pCurrentAstSizeSave = m_pCurrentAstSize;
+    state->m_nextBlockId = m_nextBlockId;
 
     Assert(state->m_ppnodeScopeSave == nullptr || *state->m_ppnodeScopeSave == nullptr);
     Assert(state->m_ppnodeExprScopeSave == nullptr || *state->m_ppnodeExprScopeSave == nullptr);
@@ -1938,6 +1939,7 @@ void Parser::RestoreStateFrom(ParserState *state)
     m_funcInArrayDepth = state->m_funcInArrayDepthSave;
     *m_pnestedCount = state->m_nestedCountSave;
     m_pCurrentAstSize = state->m_pCurrentAstSizeSave;
+    m_nextBlockId = state->m_nextBlockId;
 
     if (state->m_ppnodeScopeSave != nullptr)
     {
@@ -8277,8 +8279,18 @@ ParseNodePtr Parser::ParseExpr(int oplMin,
         {
             m_pscan->SeekTo(termStart);
 
+            // As we are reparsing from the beginning of the destructured literal we need to reset the Block IDs as well to make sure the Block IDs
+            // on the pidref stack match.
+            int saveNextBlockId = m_nextBlockId;
+            m_nextBlockId = parserState.m_nextBlockId;
+
             ParseDestructuredLiteralWithScopeSave(tkLCurly, false/*isDecl*/, false /*topLevel*/, DIC_ShouldNotParseInitializer);
 
+            // Restore the Block ID at the end of the reparsing so it matches the one at the end of the first pass. We need to do this 
+            // because we don't parse initializers during reparse and there may be additional blocks (e.g. a class declaration)
+            // in the initializers that will cause the next Block ID at the end of the reparsing to be different.
+            m_nextBlockId = saveNextBlockId;
+
             if (buildAST)
             {
                 pnode = ConvertToPattern(pnode);
@@ -12311,12 +12323,17 @@ ParseNodePtr Parser::ParseDestructuredVarDecl(tokens declarationType, bool isDec
     int parenCount = 0;
     bool seenRest = false;
 
-    while (m_token.tk == tkLParen)
+    // Save the Block ID prior to the increments, so we can restore it back.
+    int originalCurrentBlockId = GetCurrentBlock()->sxBlock.blockId;
+
+    // Eat the left parentheses only when its not a declaration. This will make sure we throw syntax errors early.
+    if (!isDecl)
     {
-        m_pscan->Scan();
-        ++parenCount;
-        if (m_reparsingLambdaParams)
+        while (m_token.tk == tkLParen)
         {
+            m_pscan->Scan();
+            ++parenCount;
+
             // Match the block increment we do upon entering parenthetical expressions
             // so that the block ID's will match on reparsing of parameters.
             GetCurrentBlock()->sxBlock.blockId = m_nextBlockId++;
@@ -12331,10 +12348,18 @@ ParseNodePtr Parser::ParseDestructuredVarDecl(tokens declarationType, bool isDec
         seenRest = true;
         m_pscan->Scan();
 
-        while (m_token.tk == tkLParen)
+        // Eat the left parentheses only when its not a declaration. This will make sure we throw syntax errors early.
+        if (!isDecl)
         {
-            m_pscan->Scan();
-            ++parenCount;
+            while (m_token.tk == tkLParen)
+            {
+                m_pscan->Scan();
+                ++parenCount;
+
+                // Match the block increment we do upon entering parenthetical expressions
+                // so that the block ID's will match on reparsing of parameters.
+                GetCurrentBlock()->sxBlock.blockId = m_nextBlockId++;
+            }
         }
 
         if (m_token.tk != tkID && m_token.tk != tkSUPER && m_token.tk != tkLCurly && m_token.tk != tkLBrack)
@@ -12413,10 +12438,14 @@ ParseNodePtr Parser::ParseDestructuredVarDecl(tokens declarationType, bool isDec
     }
 
     // Swallow RParens before a default expression, if any.
-    while (m_token.tk == tkRParen)
+    // We eat the left parentheses only when its not a declaration. This will make sure we throw syntax errors early. We need to do the same for right parentheses.
+    if (!isDecl)
     {
-        m_pscan->Scan();
-        --parenCount;
+        while (m_token.tk == tkRParen)
+        {
+            m_pscan->Scan();
+            --parenCount;
+        }
     }
 
     if (hasSeenRest != nullptr)
@@ -12454,10 +12483,17 @@ ParseNodePtr Parser::ParseDestructuredVarDecl(tokens declarationType, bool isDec
         pnodeElem = pnodeRest;
     }
 
-    while (m_token.tk == tkRParen)
+    // We eat the left parentheses only when its not a declaration. This will make sure we throw syntax errors early. We need to do the same for right parentheses.
+    if (!isDecl)
     {
-        m_pscan->Scan();
-        --parenCount;
+        while (m_token.tk == tkRParen)
+        {
+            m_pscan->Scan();
+            --parenCount;
+        }
+
+        // Restore the Block ID of the current block after the parsing of destructured variable declarations and initializers.
+        GetCurrentBlock()->sxBlock.blockId = originalCurrentBlockId;
     }
 
     if (!(m_token.tk == tkComma || m_token.tk == tkRBrack || m_token.tk == tkRCurly))

+ 1 - 0
lib/Parser/Parse.h

@@ -531,6 +531,7 @@ private:
         int32 *m_pCurrentAstSizeSave;
         uint m_funcInArrayDepthSave;
         uint m_nestedCountSave;
+        int m_nextBlockId;
 #if DEBUG
         // For very basic validation purpose - to check that we are not going restore to some other block.
         BlockInfoStack *m_currentBlockInfo;

+ 15 - 15
test/es6/destructuring.js

@@ -170,22 +170,22 @@ var tests = [
       assert.doesNotThrow(function () { eval("var a, b; [a, [b]] = [1, []];"); }, "Destructured var array assignment with some nesting does not throw");
       assert.doesNotThrow(function () { eval("let a, b; [a, [b]] = [1, []];"); }, "Destructured let array assignment with some nesting does not throw");
 
-      assert.throws(function () { eval("var [((a)] = [];"); },    SyntaxError, "Destructured var array declaration with a mismatched paren count throws",   "Expected ')'");
-      assert.throws(function () { eval("let [((a)] = [];"); },    SyntaxError, "Destructured let array declaration with a mismatched paren count throws",   "Expected ')'");
-      assert.throws(function () { eval("const [((a)] = [];"); },  SyntaxError, "Destructured const array declaration with a mismatched paren count throws", "Expected ')'");
+      assert.throws(function () { eval("var [((a)] = [];"); },    SyntaxError, "Destructured var array declaration with a mismatched paren count throws");
+      assert.throws(function () { eval("let [((a)] = [];"); },    SyntaxError, "Destructured let array declaration with a mismatched paren count throws",   "Destructuring expressions can only have identifier references");
+      assert.throws(function () { eval("const [((a)] = [];"); },  SyntaxError, "Destructured const array declaration with a mismatched paren count throws", "Destructuring expressions can only have identifier references");
       assert.throws(function () { eval("var a; [((a)] = [];"); }, SyntaxError, "Destructured var array assignment with a mismatched paren count throws",    "Expected ')'");
-      assert.throws(function () { eval("let a; [((a)] = [];"); }, SyntaxError, "Destructured let array assignment with a mismatched paren count throws",    "Expected ')'");
-      assert.throws(function () { eval("var [a)] = [];"); },      SyntaxError, "Destructured var array declaration with a mismatched paren count throws",   "Expected ')'");
-      assert.throws(function () { eval("let [a)] = [];"); },      SyntaxError, "Destructured let array declaration with a mismatched paren count throws",   "Expected ')'");
-      assert.throws(function () { eval("const [a)] = [];"); },    SyntaxError, "Destructured const array declaration with a mismatched paren count throws", "Expected ')'");
+      assert.throws(function () { eval("let a; [((a)] = [];"); }, SyntaxError, "Destructured let array assignment with a mismatched paren count throws");
+      assert.throws(function () { eval("var [a)] = [];"); },      SyntaxError, "Destructured var array declaration with a mismatched paren count throws");
+      assert.throws(function () { eval("let [a)] = [];"); },      SyntaxError, "Destructured let array declaration with a mismatched paren count throws");
+      assert.throws(function () { eval("const [a)] = [];"); },    SyntaxError, "Destructured const array declaration with a mismatched paren count throws");
       assert.throws(function () { eval("var a; [a)] = [];"); },   SyntaxError, "Destructured var array assignment with a mismatched paren count throws",    "Expected ']'");
-      assert.throws(function () { eval("let a; [a)] = [];"); },   SyntaxError, "Destructured let array assignment with a mismatched paren count throws",    "Expected ']'");
-      assert.doesNotThrow(function () { eval("var [((((a)))), b] = [];"); },       "Destructured var array declaration with some nested parens does not throw");
-      assert.doesNotThrow(function () { eval("let [((((a)))), b] = [];"); },       "Destructured let array declaration with some nested parens does not throw");
-      assert.doesNotThrow(function () { eval("const [((((a)))), b] = [];"); },     "Destructured const array declaration with some nested parens does not throw");
+      assert.throws(function () { eval("let a; [a)] = [];"); },   SyntaxError, "Destructured let array assignment with a mismatched paren count throws");
+      assert.throws(function () { eval("var [((((a)))), b] = [];"); }, SyntaxError, "Destructured var array declaration with some nested parens does not throw");
+      assert.throws(function () { eval("let [((((a)))), b] = [];"); }, SyntaxError, "Destructured let array declaration with some nested parens does not throw");
+      assert.throws(function () { eval("const [((((a)))), b] = [];"); }, SyntaxError, "Destructured const array declaration with some nested parens does not throw");
+      
       assert.doesNotThrow(function () { eval("var a, b; [((((a)))), b] = [];"); }, "Destructured var array assignment with some nested parens does not throw");
       assert.doesNotThrow(function () { eval("let a, b; [((((a)))), b] = [];"); }, "Destructured let array assignment with some nested parens does not throw");
-
       assert.doesNotThrow(function () { eval("var [[[...a]]] = [[[]]];"); },    "Destructured var array declaration with nested rest parameter does not throw");
       assert.doesNotThrow(function () { eval("let [[[...a]]] = [[[]]];"); },    "Destructured let array declaration with nested rest parameter does not throw");
       assert.doesNotThrow(function () { eval("const [[[...a]]] = [[[]]];"); },  "Destructured const array declaration with nested rest parameter does not throw");
@@ -198,9 +198,9 @@ var tests = [
       assert.doesNotThrow(function () { eval("var a, b; [[...a], ...b] = [[],];"); }, "Destructured var array assignment with valid nested rest parameters does not throw");
       assert.doesNotThrow(function () { eval("let a, b; [[...a], ...b] = [[],];"); }, "Destructured let array assignment with valid nested rest parameters does not throw");
 
-      assert.doesNotThrow(function () { eval("var [[(a)], ((((((([b])))))))] = [[],[]];"); },       "Destructured var array declaration with valid mixed paren and array nesting does not throw");
-      assert.doesNotThrow(function () { eval("let [[(a)], ((((((([b])))))))] = [[],[]];"); },       "Destructured let array declaration with valid mixed paren and array nesting does not throw");
-      assert.doesNotThrow(function () { eval("const [[(a)], ((((((([b])))))))] = [[],[]];"); },     "Destructured const array declaration with valid mixed paren and array nesting does not throw");
+      assert.throws(function () { eval("var [[(a)], ((((((([b])))))))] = [[],[]];"); }, SyntaxError, "Destructured var array declaration with valid mixed paren and array nesting does not throw");
+      assert.throws(function () { eval("let [[(a)], ((((((([b])))))))] = [[],[]];"); }, SyntaxError, "Destructured let array declaration with valid mixed paren and array nesting does not throw");
+      assert.throws(function () { eval("const [[(a)], ((((((([b])))))))] = [[],[]];"); }, SyntaxError, "Destructured const array declaration with valid mixed paren and array nesting does not throw");
       assert.doesNotThrow(function () { eval("var a, b; [[(a)], ((((((([b])))))))] = [[],[]];"); }, "Destructured var array assignment with valid mixed paren and array nesting does not throw");
       assert.doesNotThrow(function () { eval("let a, b; [[(a)], ((((((([b])))))))] = [[],[]];"); }, "Destructured let array assignment with valid mixed paren and array nesting does not throw");
 

+ 93 - 1
test/es6/destructuring_bugs.js

@@ -360,7 +360,99 @@ var tests = [
         assert.areEqual(nextCount, 1);
         assert.areEqual(returnCount, 1);
     }
-  }
+  },
+  {
+    name: "Destructuring pattern at param has nested blocks.",
+    body: function () {
+        assert.doesNotThrow(function () { eval("var e = 1;       ( {abcdef  = ((((({})) = (1))))} = (e)) => {  try{ } catch(e) {}}") }, "Should not throw when the default value and it's initializer both have extra parentheses.");
+
+        assert.doesNotThrow(function () { eval("var e = 1;       ( {ghijkl  = ((((({})) =  1 )))} = (e)) => {  try{ } catch(e) {}}") }, "Should not throw when the default value has extra parentheses.");
+
+        assert.doesNotThrow(function () { eval("var e = 1; ( {mnopqrs = (((  {}   = (1))))} = (e)) => {  try{ } catch(e) {}}") }, "Should not throw when the default value initializer has extra parentheses.");
+
+        assert.doesNotThrow(function () { eval("var e = 1; ( {tuvwxy  = (((  {}   =  1 )))} = (e)) => {  try{ } catch(e) {}}") }, "Should not throw when the default value and initializer are wrapped in extra parentheses.");
+
+        assert.doesNotThrow(function () { eval("var e = 1;       ( {abcdef  = (((((foo)) = (1))))} = (e)) => {  try{ } catch(e) {}}") }, "Should not throw when the default value and it's initializer both have extra parentheses.");
+
+        assert.doesNotThrow(function () { eval("var e = 1;       ( {ghijkl  = (((((foo)) =  1 )))} = (e)) => {  try{ } catch(e) {}}") }, "Should not throw when the default value has extra parentheses.");
+
+        assert.doesNotThrow(function () { eval("var e = 1; ( {mnopqrs = (((  foo   = (1))))} = (e)) => {  try{ } catch(e) {}}") }, "Should not throw when the default value initializer has extra parentheses.");
+
+        assert.doesNotThrow(function () { eval("var e = 1; ( {tuvwxy  = (((  foo   =  1 )))} = (e)) => {  try{ } catch(e) {}}") }, "Should not throw when the default value and initializer are wrapped in extra parentheses.");
+
+        assert.doesNotThrow(function () { eval("var a; ({ tyssjh = ((cspagh = 4) => a) } = 1) => { /*jjj*/ }; (function(a) { })()") }, "Should not throw when there is a nested lambda expression within destructured variable declaration.");
+
+        assert.doesNotThrow(function () { eval("var a; [a = class aClass {}] = [];") }, "Should not throw when class declaration exists in destructured variable initializer.");
+
+        assert.throws(function () { eval("function test5(){ var ggnzrk=function(){ }; ({ggnzrk, namespace: {}, w: [(inmgdv)]}) => { };};") }, SyntaxError, "Should throw if nested destructured declaration has a variable name in parenthesis.");
+
+        assert.throws(function () { eval("function test5(){ var ggnzrk=function(){ }; ({ggnzrk, namespace: {}, w: ([inmgdv])}) => { };};") }, SyntaxError, "Should throw if nested destructured declaration is wrapped in extra parenthesis.");
+
+        assert.throws(function () { eval("{ ([((iydvhw)), gpvpgk]) => { }; } var iydvhw=function(){return this};") }, SyntaxError, "Should throw if variable names in destructured declaration have extra parentheses.");
+
+        assert.throws(function () { eval("(nmlwii, [((yycokb) = (1))] = (nmlwii)) => { };") }, SyntaxError, "Should throw if one or more of the lambda parameters have destructured variable declaration with initializer capturing another parameter.");
+
+        assert.throws(function () { eval("({ggnzrk, w: (ggnzrk)}) => { };") }, SyntaxError, "Should throw if reused symbol in destructured variable declaration.");
+
+        assert.throws(function () { eval("([x, ...((yArgs))]) => {}") }, SyntaxError, "Should throw if rest parameter name in a declaration is enclosed in parenthesis.");
+
+        assert.throws(function () { eval("([x, ...(([yArgs, zArgs]))]) => {}") }, SyntaxError, "Should throw if rest parameter name in a declaration is enclosed in parenthesis.");
+
+        assert.doesNotThrow(function () { eval("( {abcdef  = ((((([...((abcdef))] = [1, 2, 3])) = (1))))} = (e)) => {  try{  } catch(abcdef) {}}") }, "Should not throw when rest parameter name is enclosed in extra parentheses.");
+
+        var a1 = 10;
+        var b1 = 20;
+        bar1 = ( {abcdef  = (((((a1)) = (30))))} = (b1 = 40) ) => { try { throw a1; } catch(a1) { } };
+        bar1();
+        assert.areEqual(a1, 30, "The default value initializer should have fired for parameter in destructuring pattern.");
+        assert.areEqual(b1, 40, "The default value block initializer should have fired for parameter in destructuring pattern.");
+
+        var a2 = 10;
+        var b2 = 20;
+        bar2 = ( {abcdef  = (((((a2)) = (30))))} = (b2 = 40) ) => { try { throw a2; } catch(a2) { } };
+        bar2({abcdef : 50});
+        assert.areEqual(a2, 10, "The default value initializer should NOT have fired for parameter in destructuring pattern.");
+        assert.areEqual(b2, 20, "The default value block initializer should NOT have fired for parameter in destructuring pattern.");
+
+        var a3 = 10;
+        var b3 = 20;
+        bar3 = ( {aa3 = a3, bb3 = b3, abcdef  = (((((a3)) = (30))))} = (b3 = 40) ) => 
+        { 
+            try 
+            {
+                assert.areEqual(a3, undefined, "The variable a3 in the current scope should not have been assigned yet.");
+                assert.areEqual(b3, undefined, "The variable b3 in the current scope should not have been assigned yet.");
+                assert.areEqual(aa3, 10, "The parameter aa3 should get initialized correctly.");
+                assert.areEqual(bb3, 20, "The parameter bb3 should get initialized correctly.");
+                var a3 = 60; 
+                var b3 = 70; 
+                throw a3; 
+            } catch(a3) { } 
+        };
+        bar3({abcdef : 50});
+        assert.areEqual(a3, 10, "The variable a3 in the enclosing scope should not have been changed.");
+        assert.areEqual(b3, 20, "The variable a3 in the enclosing scope should not have been changed.");
+
+        var a4 = 10;
+        var b4 = 15;
+        bar4 = ( { b4 = ((xyz = 4) => a4) } = 1) => { b4 = 35; return b4; }; 
+        var c4 = bar4();
+        var d4 = (function( { a4, b4 } = { a4 : 20, b4 : 25 }) { return a4;})();
+        assert.areEqual(a4, 10, "The variable in the enclosing scope should not have been changed.");
+        assert.areEqual(b4, 15, "The variable in the enclosing scope should not have been changed.");
+        assert.areEqual(c4, 35, "The variable in the enclosing scope should not have been changed.");
+        assert.areEqual(d4, 20, "The variable in the enclosing scope should not have been changed.");
+    }
+},
+{
+    name: "Destructuring pattern with rest parameter has nested blocks.",
+    body: function () {
+    [...((a5))] = [1, 2, 3];
+    assert.areEqual(a5, [1, 2, 3], "The rest parameter with extra parentheses gets assigned correctly.");
+
+    assert.doesNotThrow(function () { eval("[...((a))] = [1, 2, 3]") }, "Should not throw when rest parameter name is enclosed in extra parentheses.");
+    }
+}
 ];
 
 testRunner.runTests(tests, { verbose: WScript.Arguments[0] != "summary" });

+ 0 - 5
test/es6/lambda-params-shadow.js

@@ -22,9 +22,4 @@ if (count !== 3) {
     WScript.Echo('fail');
 }
 
-{
-    ([((iydvhw)), gpvpgk]) => {/*jjj*/};
-}
-var iydvhw=function(){return this};
-
 WScript.Echo('pass');