Browse Source

Add simple set of captured names to nested ParseNodeFnc

Each `ParseNodeFnc` has an IdentPtrSet which holds the set of names captured by the function. Captured names are ones which are referenced inside the function but not declared within the function.

Implement this by intercepting name references at PushPidRef and keep the name reference in the set of captured names for the current nested function. Remove any names from the captured name list which do not have dangling references after we've bound the pid ref stack for a nested function. When finished parsing a nested function, add the set of captured names onto the parent functions' captured name set.

Gate the feature via a ParseType flag and allow a forcing config option (`CreateParserState`).

Example captured name set:
```js
let x, y, z;
function foo() {
  function bar() {
    return x;
  }
  function baz(x) {
    return y;
  }
  function bat() {
    {
      let z;
      return z;
    }
  }
}

// captured name sets
// foo: [x, y]
// bar: [x]
// baz: [y]
// bat: []
```
Taylor Woll 8 năm trước cách đây
mục cha
commit
450c1d1d30

+ 3 - 0
lib/Common/ConfigFlagsList.h

@@ -15,6 +15,7 @@ PHASE(All)
         PHASE(DeferEventHandlers)
         PHASE(FunctionSourceInfoParse)
         PHASE(StringTemplateParse)
+        PHASE(CreateParserState)
         PHASE(SkipNestedDeferred)
         PHASE(CacheScopeInfoNames)
         PHASE(ScanAhead)
@@ -427,6 +428,7 @@ PHASE(All)
 #define DEFAULT_CONFIG_PrintLineColumnInfo (false)
 #define DEFAULT_CONFIG_ForceDecommitOnCollect (false)
 #define DEFAULT_CONFIG_ForceDeferParse      (false)
+#define DEFAULT_CONFIG_ForceCreateParserState (false)
 #define DEFAULT_CONFIG_NoDeferParse         (false)
 #define DEFAULT_CONFIG_ForceDynamicProfile  (false)
 #define DEFAULT_CONFIG_ForceExpireOnNonCacheCollect (false)
@@ -1144,6 +1146,7 @@ FLAGNR(Boolean, ForceCleanCacheOnCollect, "Force cleaning of dynamic caches on c
 FLAGNR(Boolean, ForceGCAfterJSONParse, "Force GC to happen after JSON parsing", DEFAULT_CONFIG_ForceGCAfterJSONParse)
 FLAGNR(Boolean, ForceDecommitOnCollect, "Force decommit collect", DEFAULT_CONFIG_ForceDecommitOnCollect)
 FLAGNR(Boolean, ForceDeferParse       , "Defer parsing of all function bodies", DEFAULT_CONFIG_ForceDeferParse)
+FLAGNR(Boolean, ForceCreateParserState , "Force creation of parser state", DEFAULT_CONFIG_ForceCreateParserState)
 FLAGNR(Boolean, ForceDiagnosticsMode  , "Enable diagnostics mode and debug interpreter loop", false)
 FLAGNR(Boolean, ForceGetWriteWatchOOM , "Force GetWriteWatch to go into OOM codepath in HeapBlockMap rescan", false)
 FLAGNR(Boolean, ForcePostLowerGlobOptInstrString, "Force tracking of globopt instr string post lower", DEFAULT_CONFIG_ForcePostLowerGlobOptInstrString)

+ 69 - 3
lib/Parser/Parse.cpp

@@ -4881,7 +4881,6 @@ ParseNode * Parser::ParseFncDecl(ushort flags, LPCOLESTR pNameHint, const bool n
     pnodeFnc->cbMin = this->GetScanner()->IecpMinTok();
     pnodeFnc->functionId = (*m_nextFunctionId)++;
 
-
     // Push new parser state with this new function node
 
     AppendFunctionToScopeList(fDeclaration, pnodeFnc);
@@ -4918,6 +4917,9 @@ ParseNode * Parser::ParseFncDecl(ushort flags, LPCOLESTR pNameHint, const bool n
     IdentPtr pFncNamePid = nullptr;
     bool needScanRCurly = true;
     bool result = ParseFncDeclHelper<buildAST>(pnodeFnc, pNameHint, flags, &funcHasName, fUnaryOrParen, noStmtContext, &needScanRCurly, fModule, &pFncNamePid);
+
+    AddNestedCapturedNames(pnodeFnc);
+
     if (!result)
     {
         Assert(!pnodeFncBlockScope);
@@ -5659,6 +5661,8 @@ bool Parser::ParseFncDeclHelper(ParseNodeFnc * pnodeFnc, LPCOLESTR pNameHint, us
             CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, StrictModeFunction, m_scriptContext);
         }
 
+        ProcessCapturedNames(pnodeFnc);
+
         if (fDeferred)
         {
             AnalysisAssert(pnodeFnc);
@@ -9029,19 +9033,27 @@ void Parser::TrackAssignment(ParseNodePtr pnodeT, IdentToken* pToken)
 
 PidRefStack* Parser::PushPidRef(IdentPtr pid)
 {
+    ParseNodeFnc* currentFnc = GetCurrentFunctionNode();
+
+    if (this->IsCreatingStateCache())
+    {
+        IdentPtrSet* capturedNames = currentFnc->EnsureCapturedNames(&m_nodeAllocator);
+        capturedNames->AddNew(pid);
+    }
+
     if (PHASE_ON1(Js::ParallelParsePhase))
     {
         // NOTE: the phase check is here to protect perf. See OSG 1020424.
         // In some LS AST-rewrite cases we lose a lot of perf searching the PID ref stack rather
         // than just pushing on the top. This hasn't shown up as a perf issue in non-LS benchmarks.
-        return pid->FindOrAddPidRef(&m_nodeAllocator, GetCurrentBlock()->blockId, GetCurrentFunctionNode()->functionId);
+        return pid->FindOrAddPidRef(&m_nodeAllocator, GetCurrentBlock()->blockId, currentFnc->functionId);
     }
 
     Assert(GetCurrentBlock() != nullptr);
     AssertMsg(pid != nullptr, "PID should be created");
     PidRefStack *ref = pid->GetTopRef(m_nextBlockId - 1);
     int blockId = GetCurrentBlock()->blockId;
-    int funcId = GetCurrentFunctionNode()->functionId;
+    int funcId = currentFnc->functionId;
     if (!ref || (ref->GetScopeId() < blockId))
     {
         ref = Anew(&m_nodeAllocator, PidRefStack);
@@ -13826,6 +13838,59 @@ void ParseNode::Dump()
 }
 #endif
 
+void Parser::AddNestedCapturedNames(ParseNodeFnc* pnodeChildFnc)
+{
+    if (m_currentNodeFunc && this->IsCreatingStateCache() && pnodeChildFnc->HasAnyCapturedNames())
+    {
+        IdentPtrSet* parentCapturedNames = GetCurrentFunctionNode()->EnsureCapturedNames(&m_nodeAllocator);
+        IdentPtrSet* childCaptureNames = pnodeChildFnc->GetCapturedNames();
+
+        auto iter = childCaptureNames->GetIterator();
+
+        while (iter.IsValid())
+        {
+            parentCapturedNames->AddNew(iter.CurrentValue());
+            iter.MoveNext();
+        }
+    }
+}
+
+void Parser::ProcessCapturedNames(ParseNodeFnc* pnodeFnc)
+{
+    if (this->IsCreatingStateCache() && pnodeFnc->HasAnyCapturedNames())
+    {
+        IdentPtrSet* capturedNames = pnodeFnc->GetCapturedNames();
+        auto iter = capturedNames->GetIteratorWithRemovalSupport();
+
+        while (iter.IsValid())
+        {
+            const IdentPtr& pid = iter.CurrentValueReference();
+            PidRefStack* ref = pid->GetTopRef();
+
+            // If the pid has no refs left in our function's scope after binding, we didn't capture it.
+            if (!ref || ref->GetScopeId() < pnodeFnc->pnodeBodyScope->blockId)
+            {
+                iter.RemoveCurrent();
+            }
+            else
+            {
+                OUTPUT_TRACE_DEBUGONLY(Js::CreateParserStatePhase, _u("Function %u captured name \"%s\"\n"), pnodeFnc->functionId, pid->Psz());
+            }
+
+            iter.MoveNext();
+        }
+    }
+}
+
+bool Parser::IsCreatingStateCache()
+{
+    return this->m_parseType == ParseType_StateCache
+#ifdef ENABLE_DEBUG_CONFIG_OPTIONS
+        || CONFIG_FLAG(ForceCreateParserState)
+#endif
+            ;
+}
+
 DeferredFunctionStub * BuildDeferredStubTree(ParseNodeFnc *pnodeFnc, Recycler *recycler)
 {
     Assert(pnodeFnc->nop == knopFncDecl);
@@ -13887,6 +13952,7 @@ DeferredFunctionStub * BuildDeferredStubTree(ParseNodeFnc *pnodeFnc, Recycler *r
         deferredStubs[i].restorePoint = *pnodeFncChild->pRestorePoint;
         deferredStubs[i].deferredStubs = BuildDeferredStubTree(pnodeFncChild, recycler);
         deferredStubs[i].ichMin = pnodeChild->ichMin;
+
         ++i;
         pnodeChild = pnodeFncChild->pnodeNext;
     }

+ 9 - 1
lib/Parser/Parse.h

@@ -36,7 +36,8 @@ enum
 enum ParseType
 {
     ParseType_Upfront,
-    ParseType_Deferred
+    ParseType_Deferred,
+    ParseType_StateCache
 };
 
 enum DestructuringInitializerContext
@@ -117,6 +118,8 @@ public:
 
     Js::ScriptContext* GetScriptContext() const { return m_scriptContext; }
 
+    bool IsCreatingStateCache();
+
 #if ENABLE_BACKGROUND_PARSING
     bool IsBackgroundParser() const { return m_isInBackground; }
     bool IsDoingFastScan() const { return m_doingFastScan; }
@@ -317,6 +320,8 @@ public:
                 return _u("Upfront");
             case ParseType_Deferred:
                 return _u("Deferred");
+            case ParseType_StateCache:
+                return _u("StateCache");
         }
         Assert(false);
         return NULL;
@@ -684,6 +689,9 @@ private:
     ParseNodeSpecialName * ReferenceSpecialName(IdentPtr pid, charcount_t ichMin = 0, charcount_t ichLim = 0, bool createNode = false);
     ParseNodeVar * CreateSpecialVarDeclIfNeeded(ParseNodeFnc * pnodeFnc, IdentPtr pid, bool forceCreate = false);
 
+    void ProcessCapturedNames(ParseNodeFnc* pnodeFnc);
+    void AddNestedCapturedNames(ParseNodeFnc* pnodeChildFnc);
+
     template<const bool backgroundPidRefs>
     void BindPidRefs(BlockInfoStack *blockInfo, uint maxBlockId = (uint)-1);
     void BindPidRefsInScope(IdentPtr pid, Symbol *sym, int blockId, uint maxBlockId = (uint)-1);

+ 1 - 0
lib/Parser/ParserCommon.h

@@ -51,6 +51,7 @@ struct ModuleImportOrExportEntry
 
 typedef SList<ModuleImportOrExportEntry, ArenaAllocator> ModuleImportOrExportEntryList;
 typedef SList<IdentPtr, ArenaAllocator> IdentPtrList;
+typedef JsUtil::BaseHashSet<IdentPtr, ArenaAllocator, PowerOf2SizePolicy> IdentPtrSet;
 
 //
 // Below was moved from scrutil.h to share with chakradiag.

+ 21 - 2
lib/Parser/ptree.cpp

@@ -433,7 +433,7 @@ ParseNodeFnc::ParseNodeFnc(OpCode nop, charcount_t ichMin, charcount_t ichLim)
     this->pRestorePoint = nullptr;
     this->deferredStub = nullptr;
 
-    
+    this->capturedNames = nullptr;
 }
 
 ParseNodeClass::ParseNodeClass(OpCode nop, charcount_t ichMin, charcount_t ichLim)
@@ -605,4 +605,23 @@ ParseNodeSuperCall::ParseNodeSuperCall(OpCode nop, charcount_t ichMin, charcount
     this->isSuperCall = true;
     this->pnodeThis = nullptr;
     this->pnodeNewTarget = nullptr;
-}
+}
+
+IdentPtrSet* ParseNodeFnc::EnsureCapturedNames(ArenaAllocator* alloc)
+{
+    if (this->capturedNames == nullptr)
+    {
+        this->capturedNames = Anew(alloc, IdentPtrSet, alloc);
+    }
+    return this->capturedNames;
+}
+
+IdentPtrSet* ParseNodeFnc::GetCapturedNames()
+{
+    return this->capturedNames;
+}
+
+bool ParseNodeFnc::HasAnyCapturedNames()
+{
+    return this->capturedNames != nullptr && this->capturedNames->Count() != 0;
+}

+ 5 - 0
lib/Parser/ptree.h

@@ -490,6 +490,7 @@ public:
 #endif
     RestorePoint *pRestorePoint;
     DeferredFunctionStub *deferredStub;
+    IdentPtrSet *capturedNames;
     bool canBeDeferred;
     bool isBodyAndParamScopeMerged; // Indicates whether the param scope and the body scope of the function can be merged together or not.
                                     // We cannot merge both scopes together if there is any closure capture or eval is present in the param scope.
@@ -641,6 +642,10 @@ public:
         }
     }
 
+    IdentPtrSet* EnsureCapturedNames(ArenaAllocator* alloc);
+    IdentPtrSet* GetCapturedNames();
+    bool HasAnyCapturedNames();
+
     DISABLE_SELF_CAST(ParseNodeFnc);
 };
 

+ 149 - 0
test/Basics/VerifyParserState.baseline

@@ -0,0 +1,149 @@
+nested function capturing var decl
+CreateParserState:Function 2 captured name "a1"
+
+nested function capturing a let decl
+CreateParserState:Function 2 captured name "a2"
+
+nested function capturing a const decl
+CreateParserState:Function 2 captured name "a3"
+
+nested function capturing non-decl names
+CreateParserState:Function 2 captured name "a4"
+CreateParserState:Function 2 captured name "b4"
+CreateParserState:Function 1 captured name "a4"
+CreateParserState:Function 1 captured name "b4"
+
+nested function referencing a local shadowing formal
+
+nested function capturing a named formal
+CreateParserState:Function 2 captured name "a6"
+
+nested functions capturing global declarations
+CreateParserState:Function 3 captured name "d7"
+CreateParserState:Function 3 captured name "v7"
+CreateParserState:Function 3 captured name "a7"
+CreateParserState:Function 3 captured name "c7"
+CreateParserState:Function 3 captured name "b7"
+CreateParserState:Function 3 captured name "l7"
+CreateParserState:Function 2 captured name "d7"
+CreateParserState:Function 2 captured name "v7"
+CreateParserState:Function 2 captured name "a7"
+CreateParserState:Function 2 captured name "c7"
+CreateParserState:Function 2 captured name "l7"
+CreateParserState:Function 1 captured name "d7"
+CreateParserState:Function 1 captured name "v7"
+CreateParserState:Function 1 captured name "b7"
+CreateParserState:Function 1 captured name "c7"
+CreateParserState:Function 1 captured name "l7"
+
+nested function capturing a class decl
+CreateParserState:Function 3 captured name "class_1"
+CreateParserState:Function 2 captured name "class_1"
+
+object literal methods capturing names
+CreateParserState:Function 1 captured name "v9"
+CreateParserState:Function 1 captured name "o_9"
+CreateParserState:Function 1 captured name "c9"
+CreateParserState:Function 1 captured name "l9"
+CreateParserState:Function 1 captured name "b9"
+CreateParserState:Function 2 captured name "v9"
+CreateParserState:Function 2 captured name "o_9"
+CreateParserState:Function 2 captured name "c9"
+CreateParserState:Function 2 captured name "l9"
+CreateParserState:Function 2 captured name "b9"
+CreateParserState:Function 3 captured name "v9"
+CreateParserState:Function 3 captured name "o_9"
+CreateParserState:Function 3 captured name "c9"
+CreateParserState:Function 3 captured name "l9"
+CreateParserState:Function 3 captured name "b9"
+CreateParserState:Function 4 captured name "v9"
+CreateParserState:Function 4 captured name "o_9"
+CreateParserState:Function 4 captured name "c9"
+CreateParserState:Function 4 captured name "l9"
+CreateParserState:Function 4 captured name "b9"
+CreateParserState:Function 5 captured name "v9"
+CreateParserState:Function 5 captured name "o_9"
+CreateParserState:Function 5 captured name "c9"
+CreateParserState:Function 5 captured name "l9"
+CreateParserState:Function 5 captured name "b9"
+CreateParserState:Function 6 captured name "v9"
+CreateParserState:Function 6 captured name "a9"
+CreateParserState:Function 6 captured name "o_9"
+CreateParserState:Function 6 captured name "c9"
+CreateParserState:Function 6 captured name "l9"
+CreateParserState:Function 6 captured name "b9"
+CreateParserState:Function 7 captured name "v9"
+CreateParserState:Function 7 captured name "o_9"
+CreateParserState:Function 7 captured name "c9"
+CreateParserState:Function 7 captured name "l9"
+CreateParserState:Function 7 captured name "b9"
+
+class members capturing names
+CreateParserState:Function 1 captured name "class_10"
+CreateParserState:Function 1 captured name "c10"
+CreateParserState:Function 1 captured name "l10"
+CreateParserState:Function 1 captured name "b10"
+CreateParserState:Function 1 captured name "v10"
+CreateParserState:Function 2 captured name "class_10"
+CreateParserState:Function 2 captured name "c10"
+CreateParserState:Function 2 captured name "l10"
+CreateParserState:Function 2 captured name "b10"
+CreateParserState:Function 2 captured name "v10"
+CreateParserState:Function 3 captured name "class_10"
+CreateParserState:Function 3 captured name "c10"
+CreateParserState:Function 3 captured name "l10"
+CreateParserState:Function 3 captured name "b10"
+CreateParserState:Function 3 captured name "v10"
+CreateParserState:Function 4 captured name "class_10"
+CreateParserState:Function 4 captured name "c10"
+CreateParserState:Function 4 captured name "l10"
+CreateParserState:Function 4 captured name "b10"
+CreateParserState:Function 4 captured name "v10"
+CreateParserState:Function 5 captured name "class_10"
+CreateParserState:Function 5 captured name "c10"
+CreateParserState:Function 5 captured name "l10"
+CreateParserState:Function 5 captured name "b10"
+CreateParserState:Function 5 captured name "v10"
+CreateParserState:Function 6 captured name "class_10"
+CreateParserState:Function 6 captured name "c10"
+CreateParserState:Function 6 captured name "l10"
+CreateParserState:Function 6 captured name "b10"
+CreateParserState:Function 6 captured name "v10"
+CreateParserState:Function 6 captured name "a10"
+CreateParserState:Function 7 captured name "class_10"
+CreateParserState:Function 7 captured name "c10"
+CreateParserState:Function 7 captured name "l10"
+CreateParserState:Function 7 captured name "b10"
+CreateParserState:Function 7 captured name "v10"
+
+lambda capturing special names
+CreateParserState:Function 2 captured name "arguments"
+CreateParserState:Function 3 captured name "*this*"
+CreateParserState:Function 4 captured name "*new.target*"
+CreateParserState:Function 6 captured name "*superconstructor*"
+CreateParserState:Function 6 captured name "*new.target*"
+CreateParserState:Function 6 captured name "*this*"
+CreateParserState:Function 5 captured name "*superconstructor*"
+CreateParserState:Function 8 captured name "*super*"
+CreateParserState:Function 8 captured name "*this*"
+CreateParserState:Function 1 captured name "*superconstructor*"
+
+nested function with shadowing block-scoped name
+CreateParserState:Function 1 captured name "v12"
+
+nested functions with a few nested captures
+CreateParserState:Function 3 captured name "b13"
+CreateParserState:Function 3 captured name "d13"
+CreateParserState:Function 3 captured name "a13"
+CreateParserState:Function 3 captured name "c13"
+CreateParserState:Function 2 captured name "b13"
+CreateParserState:Function 2 captured name "a13"
+CreateParserState:Function 5 captured name "b13"
+CreateParserState:Function 5 captured name "f13"
+CreateParserState:Function 4 captured name "e13"
+CreateParserState:Function 4 captured name "b13"
+CreateParserState:Function 4 captured name "f13"
+CreateParserState:Function 1 captured name "e13"
+CreateParserState:Function 1 captured name "f13"
+CreateParserState:Function 1 captured name "a13"
+

+ 195 - 0
test/Basics/VerifyParserState.js

@@ -0,0 +1,195 @@
+//-------------------------------------------------------------------------------------------------------
+// Copyright (C) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
+//-------------------------------------------------------------------------------------------------------
+
+// Following lines generate a baseline with flags:
+// -ForceCreateParserState -trace:CreateParserState
+//
+// Uses WScript.LoadScript so that function numbering can be easier to reason about
+
+console.log('nested function capturing var decl');
+WScript.LoadScript(`
+function f_1() {
+    var a1 = 0;
+    function f_2() {
+        var b1 = 0;
+        return a1 + b1;
+    }
+}
+`);
+console.log();
+
+console.log('nested function capturing a let decl');
+WScript.LoadScript(`
+function f_1() {
+    let a2 = 0;
+    function f_2() {
+        let b2 = 0;
+        return a2 + b2;
+    }
+}
+`);
+console.log();
+
+console.log('nested function capturing a const decl');
+WScript.LoadScript(`
+function f_1() {
+    const a3 = 0;
+    function f_2() {
+        const b3 = 0;
+        return a3 + b3;
+    }
+}
+`);
+console.log();
+
+console.log('nested function capturing non-decl names');
+WScript.LoadScript(`
+function f_1() {
+    function f_2() {
+        return a4 + b4;
+    }
+}
+`);
+console.log();
+
+console.log('nested function referencing a local shadowing formal');
+WScript.LoadScript(`
+function f_1(a5) {
+    function f_2(a5) {
+        return a5;
+    }
+}
+`);
+console.log();
+
+console.log('nested function capturing a named formal');
+WScript.LoadScript(`
+function f_1(a6) {
+    function f_2(b6) {
+        return a6 + b6;
+    }
+}
+`);
+console.log();
+
+console.log('nested functions capturing global declarations');
+WScript.LoadScript(`
+var v7;
+let l7;
+const c7 = 7;
+function f_1(a7) {
+    function f_2(b7) {
+        function f_3() {
+            return a7 + b7 + v7 + l7 + c7 + d7 + a7 + b7 + v7 + l7 + c7 + d7;
+        }
+        return a7 + b7 + v7 + l7 + c7 + d7;
+    }
+    return a7 + b7 + v7 + l7 + c7;
+}
+`);
+console.log();
+
+console.log('nested function capturing a class decl');
+WScript.LoadScript(`
+class class_1 { };
+function f_2() {
+    function f_3() {
+        return class_1;
+    }
+    return class_1;
+}
+`);
+console.log();
+
+console.log('object literal methods capturing names');
+WScript.LoadScript(`
+var v9;
+let l9;
+const c9 = 9;
+let o_9 = {
+    f_1(a9) { return a9 + b9 + c9 + l9 + v9 + o_9; },
+    *f_2(a9) { return a9 + b9 + c9 + l9 + v9 + o_9; },
+    async f_3(a9) { return a9 + b9 + c9 + l9 + v9 + o_9; },
+    ['f_4'](a9) { return a9 + b9 + c9 + l9 + v9 + o_9; },
+    *['f_5'](a9) { return a9 + b9 + c9 + l9 + v9 + o_9; },
+    get f_6() { return a9 + b9 + c9 + l9 + v9 + o_9; },
+    set f_7(a9) { a9 + b9 + c9 + l9 + v9 + o_9; }
+}
+`);
+console.log();
+
+console.log('class members capturing names');
+WScript.LoadScript(`
+var v10;
+let l10;
+const c10 = 10;
+class class_10 {
+    f_1(a10) { return a10 + b10 + c10 + l10 + v10 + class_10; }
+    *f_2(a10) { return a10 + b10 + c10 + l10 + v10 + class_10; }
+    async f_3(a10) { return a10 + b10 + c10 + l10 + v10 + class_10; }
+    ['f_4'](a10) { return a10 + b10 + c10 + l10 + v10 + class_10; }
+    *['f_5'](a10) { return a10 + b10 + c10 + l10 + v10 + class_10; }
+    get f_6() { return a10 + b10 + c10 + l10 + v10 + class_10; }
+    set f_7(a10) { a10 + b10 + c10 + l10 + v10 + class_10; }
+}
+`);
+console.log();
+
+console.log('lambda capturing special names');
+WScript.LoadScript(`
+function f_1() {
+    () => arguments; // function 2
+    () => this; // function 3
+    () => new.target; // function 4
+    
+    class c extends null {
+        constructor() { // function 5
+            () => super(); // function 6
+        }
+        method() { // function 7
+            () => super.foo(); // function 8
+        }
+    }
+}
+`);
+console.log();
+
+console.log('nested function with shadowing block-scoped name');
+WScript.LoadScript(`
+var v12;
+let l12;
+function f_1(a12) {
+    {
+        let l12;
+        return l12;
+    }
+    return v12;
+}
+`);
+console.log();
+
+console.log('nested functions with a few nested captures');
+WScript.LoadScript(`
+let a13;
+function f_1() {
+    let b13;
+    function f_2() {
+        let c13;
+        {
+            let d13;
+            function f_3() {
+                return d13 + c13 + b13 + a13;
+            }
+        }
+    }
+    function f_4() {
+        function f_5() {
+            return b13 + f13;
+        }
+        return e13;
+    }
+}
+`);
+console.log();

+ 7 - 0
test/Basics/rlexe.xml

@@ -398,4 +398,11 @@
       <compile-flags>-args summary -endargs</compile-flags>
     </default>
   </test>
+  <test>
+    <default>
+      <files>VerifyParserState.js</files>
+      <baseline>VerifyParserState.baseline</baseline>
+      <compile-flags>-forcecreateparserstate -trace:createparserstate -force:deferparse</compile-flags>
+    </default>
+  </test>
 </regress-exe>