//------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #include "ParserPch.h" #include "FormalsUtil.h" #include "../Runtime/Language/SourceDynamicProfileManager.h" #include "ByteCode/ByteCodeSerializer.h" #if DBG_DUMP void PrintPnodeWIndent(ParseNode *pnode, int indentAmt); #endif const char* const nopNames[knopLim] = { #define PTNODE(nop,sn,pc,nk,grfnop,json) sn, #include "ptlist.h" }; void printNop(int nop) { Output::Print(_u("%S\n"), nopNames[nop]); } const uint ParseNode::mpnopgrfnop[knopLim] = { #define PTNODE(nop,sn,pc,nk,grfnop,json) grfnop, #include "ptlist.h" }; bool Parser::IsES6DestructuringEnabled() const { return m_scriptContext->GetConfig()->IsES6DestructuringEnabled(); } struct BlockInfoStack { StmtNest pstmt; ParseNodeBlock *pnodeBlock; ParseNodePtr *m_ppnodeLex; // lexical variable list tail BlockInfoStack *pBlockInfoOuter; // containing block's BlockInfoStack BlockInfoStack *pBlockInfoFunction; // nearest function's BlockInfoStack (if pnodeBlock is a function, this points to itself) }; #if DEBUG Parser::Parser(Js::ScriptContext* scriptContext, BOOL strictMode, PageAllocator *alloc, bool isBackground, size_t size) #else Parser::Parser(Js::ScriptContext* scriptContext, BOOL strictMode, PageAllocator *alloc, bool isBackground) #endif : m_nodeAllocator(_u("Parser"), alloc ? alloc : scriptContext->GetThreadContext()->GetPageAllocator(), Parser::OutOfMemory), m_cactIdentToNodeLookup(0), m_grfscr(fscrNil), m_length(0), m_originalLength(0), m_nextFunctionId(nullptr), m_sourceContextInfo(nullptr), #if ENABLE_BACKGROUND_PARSING m_isInBackground(isBackground), m_hasParallelJob(false), m_doingFastScan(false), #endif m_nextBlockId(0), m_tempGuestArenaReleased(false), m_tempGuestArena(scriptContext->GetTemporaryGuestAllocator(_u("ParserRegex")), scriptContext->GetRecycler()), // use the GuestArena directly for keeping the RegexPattern* alive during byte code generation m_registeredRegexPatterns(m_tempGuestArena->GetAllocator()), m_scriptContext(scriptContext), m_token(), // should initialize to 0/nullptrs m_scan(this, &m_token, scriptContext), m_currentNodeNonLambdaFunc(nullptr), m_currentNodeNonLambdaDeferredFunc(nullptr), m_currentNodeFunc(nullptr), m_currentNodeDeferredFunc(nullptr), m_currentNodeProg(nullptr), m_currDeferredStub(nullptr), m_currDeferredStubCount(0), m_pCurrentAstSize(nullptr), m_ppnodeScope(nullptr), m_ppnodeExprScope(nullptr), m_ppnodeVar(nullptr), m_inDeferredNestedFunc(false), m_reparsingLambdaParams(false), m_disallowImportExportStmt(false), m_isInParsingArgList(false), m_hasDestructuringPattern(false), m_hasDeferredShorthandInitError(false), m_deferCommaError(false), m_pnestedCount(nullptr), wellKnownPropertyPids(), // should initialize to nullptrs m_sourceLim(0), m_functionBody(nullptr), m_parseType(ParseType_Upfront), m_arrayDepth(0), m_funcInArrayDepth(0), m_funcInArray(0), m_scopeCountNoAst(0), m_funcParenExprDepth(0), m_deferEllipsisError(false), m_deferEllipsisErrorLoc(), // calls default initializer m_deferCommaErrorLoc(), m_tryCatchOrFinallyDepth(0), m_pstmtCur(nullptr), m_currentBlockInfo(nullptr), m_currentScope(nullptr), currBackgroundParseItem(nullptr), backgroundParseItems(nullptr), fastScannedRegExpNodes(nullptr), m_currentDynamicBlock(nullptr), m_UsesArgumentsAtGlobal(false), m_fUseStrictMode(strictMode), m_InAsmMode(false), m_deferAsmJs(true), m_fExpectExternalSource(FALSE), m_deferringAST(FALSE), m_stoppedDeferredParse(FALSE) { AssertMsg(size == sizeof(Parser), "verify conditionals affecting the size of Parser agree"); Assert(scriptContext != nullptr); // init PID members InitPids(); } Parser::~Parser(void) { this->ReleaseTemporaryGuestArena(); #if ENABLE_BACKGROUND_PARSING if (this->m_hasParallelJob) { // Let the background threads know that they can decommit their arena pages. BackgroundParser *bgp = m_scriptContext->GetBackgroundParser(); Assert(bgp); if (bgp->Processor()->ProcessesInBackground()) { JsUtil::BackgroundJobProcessor *processor = static_cast(bgp->Processor()); bool result = processor->IterateBackgroundThreads([&](JsUtil::ParallelThreadData *threadData)->bool { threadData->canDecommit = true; return false; }); Assert(result); } } #endif } void Parser::OutOfMemory() { throw ParseExceptionObject(ERRnoMemory); } void Parser::Error(HRESULT hr) { throw ParseExceptionObject(hr); } void Parser::Error(HRESULT hr, ParseNodePtr pnode) { if (pnode && pnode->ichLim) { Error(hr, pnode->ichMin, pnode->ichLim); } else { Error(hr); } } void Parser::Error(HRESULT hr, charcount_t ichMin, charcount_t ichLim) { this->GetScanner()->SetErrorPosition(ichMin, ichLim); Error(hr); } void Parser::IdentifierExpectedError(const Token& token) { Assert(token.tk != tkID); HRESULT hr; if (token.IsReservedWord()) { if (token.IsKeyword()) { hr = ERRKeywordNotId; } else { Assert(token.IsFutureReservedWord(true)); if (token.IsFutureReservedWord(false)) { // Future reserved word in strict and non-strict modes hr = ERRFutureReservedWordNotId; } else { // Future reserved word only in strict mode. The token would have been converted to tkID by the scanner if not // in strict mode. Assert(IsStrictMode()); hr = ERRFutureReservedWordInStrictModeNotId; } } } else { hr = ERRnoIdent; } Error(hr); } HRESULT Parser::ValidateSyntax(LPCUTF8 pszSrc, size_t encodedCharCount, bool isGenerator, bool isAsync, CompileScriptException *pse, void (Parser::*validateFunction)()) { Assert(pszSrc); PROBE_STACK_NO_DISPOSE(m_scriptContext, Js::Constants::MinStackDefault); HRESULT hr; SmartFPUControl smartFpuControl; BOOL fDeferSave = m_deferringAST; try { hr = NOERROR; m_length = encodedCharCount; m_originalLength = encodedCharCount; // make sure deferred parsing is turned off ULONG grfscr = fscrNil; // Give the scanner the source and get the first token this->GetScanner()->SetText(pszSrc, 0, encodedCharCount, 0, false, grfscr); this->GetScanner()->SetYieldIsKeywordRegion(isGenerator); this->GetScanner()->SetAwaitIsKeywordRegion(isAsync); this->GetScanner()->Scan(); uint nestedCount = 0; m_pnestedCount = &nestedCount; ParseNodePtr pnodeScope = nullptr; m_ppnodeScope = &pnodeScope; m_ppnodeExprScope = nullptr; uint nextFunctionId = 0; m_nextFunctionId = &nextFunctionId; m_inDeferredNestedFunc = false; m_deferringAST = true; m_nextBlockId = 0; ParseNodeFnc *pnodeFnc = CreateAllowDeferNodeForOpT(); pnodeFnc->SetIsGenerator(isGenerator); pnodeFnc->SetIsAsync(isAsync); m_ppnodeVar = &pnodeFnc->pnodeVars; m_currentNodeFunc = pnodeFnc; m_currentNodeDeferredFunc = NULL; m_sourceContextInfo = nullptr; AssertMsg(m_pstmtCur == NULL, "Statement stack should be empty when we start parse function body"); ParseNodeBlock * block = StartParseBlock(PnodeBlockType::Function, ScopeType_FunctionBody); (this->*validateFunction)(); FinishParseBlock(block); pnodeFnc->ichLim = this->GetScanner()->IchLimTok(); pnodeFnc->cbLim = this->GetScanner()->IecpLimTok(); pnodeFnc->pnodeVars = nullptr; // there should be nothing after successful parsing for a given construct if (m_token.tk != tkEOF) Error(ERRsyntax); m_deferringAST = fDeferSave; } catch (ParseExceptionObject& e) { m_deferringAST = fDeferSave; hr = e.GetError(); } if (nullptr != pse && FAILED(hr)) { hr = pse->ProcessError(this->GetScanner(), hr, /* pnodeBase */ NULL); } return hr; } HRESULT Parser::ParseSourceInternal( __out ParseNodeProg ** parseTree, LPCUTF8 pszSrc, size_t offsetInBytes, size_t encodedCharCount, charcount_t offsetInChars, bool isUtf8, ULONG grfscr, CompileScriptException *pse, Js::LocalFunctionId * nextFunctionId, ULONG lineNumber, SourceContextInfo * sourceContextInfo) { Assert(parseTree); Assert(pszSrc); if (this->IsBackgroundParser()) { PROBE_STACK_NO_DISPOSE(m_scriptContext, Js::Constants::MinStackDefault); } else { PROBE_STACK(m_scriptContext, Js::Constants::MinStackDefault); } #ifdef PROFILE_EXEC m_scriptContext->ProfileBegin(Js::ParsePhase); #endif JS_ETW_INTERNAL(EventWriteJSCRIPT_PARSE_START(m_scriptContext, 0)); *parseTree = NULL; m_sourceLim = 0; m_grfscr = grfscr; m_sourceContextInfo = sourceContextInfo; ParseNodeProg * pnodeBase = NULL; HRESULT hr; SmartFPUControl smartFpuControl; try { if ((grfscr & fscrIsModuleCode) != 0) { // Module source flag should not be enabled unless module is enabled Assert(m_scriptContext->GetConfig()->IsES6ModuleEnabled()); // Module code is always strict mode code. this->m_fUseStrictMode = TRUE; } // parse the source pnodeBase = Parse(pszSrc, offsetInBytes, encodedCharCount, offsetInChars, isUtf8, grfscr, lineNumber, nextFunctionId, pse); Assert(pnodeBase); // Record the actual number of words parsed. m_sourceLim = pnodeBase->ichLim - offsetInChars; // TODO: The assert can be false positive in some scenarios and chuckj to fix it later // Assert(utf8::ByteIndexIntoCharacterIndex(pszSrc + offsetInBytes, encodedCharCount, isUtf8 ? utf8::doDefault : utf8::doAllowThreeByteSurrogates) == m_sourceLim); #if DBG_DUMP if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::ParsePhase)) { PrintPnodeWIndent(pnodeBase, 4); fflush(stdout); } #endif *parseTree = pnodeBase; hr = NOERROR; } catch (ParseExceptionObject& e) { hr = e.GetError(); } catch (Js::AsmJsParseException&) { hr = JSERR_AsmJsCompileError; } if (FAILED(hr)) { hr = pse->ProcessError(this->GetScanner(), hr, pnodeBase); } #if ENABLE_BACKGROUND_PARSING if (this->m_hasParallelJob) { ///// Wait here for remaining jobs to finish. Then look for errors, do final const bindings. // pleath TODO: If there are remaining jobs, let the main thread help finish them. BackgroundParser *bgp = m_scriptContext->GetBackgroundParser(); Assert(bgp); CompileScriptException se; this->WaitForBackgroundJobs(bgp, &se); BackgroundParseItem *failedItem = bgp->GetFailedBackgroundParseItem(); if (failedItem) { CompileScriptException *bgPse = failedItem->GetPSE(); Assert(bgPse); *pse = *bgPse; hr = failedItem->GetHR(); bgp->SetFailedBackgroundParseItem(nullptr); } if (this->fastScannedRegExpNodes != nullptr) { this->FinishBackgroundRegExpNodes(); } for (BackgroundParseItem *item = this->backgroundParseItems; item; item = item->GetNext()) { Parser *parser = item->GetParser(); parser->FinishBackgroundPidRefs(item, this != parser); } } #endif // done with the scanner this->GetScanner()->Clear(); #ifdef PROFILE_EXEC m_scriptContext->ProfileEnd(Js::ParsePhase); #endif JS_ETW_INTERNAL(EventWriteJSCRIPT_PARSE_STOP(m_scriptContext, 0)); return hr; } #if ENABLE_BACKGROUND_PARSING void Parser::WaitForBackgroundJobs(BackgroundParser *bgp, CompileScriptException *pse) { // The scan of the script is done, but there may be unfinished background jobs in the queue. // Enlist the main thread to help with those. BackgroundParseItem *item; if (!*bgp->GetPendingBackgroundItemsPtr()) { // We're done. return; } // Save parser state, since we'll need to restore it in order to bind references correctly later. this->m_isInBackground = true; this->SetCurrBackgroundParseItem(nullptr); uint blockIdSave = this->m_nextBlockId; uint functionIdSave = *this->m_nextFunctionId; StmtNest *pstmtSave = this->m_pstmtCur; if (!bgp->Processor()->ProcessesInBackground()) { // No background thread. Just walk the jobs with no locking and process them. for (item = bgp->GetNextUnprocessedItem(); item; item = bgp->GetNextUnprocessedItem()) { bgp->Processor()->RemoveJob(item); bool succeeded = bgp->Process(item, this, pse); bgp->JobProcessed(item, succeeded); } Assert(!*bgp->GetPendingBackgroundItemsPtr()); } else { // Background threads. We need to have the critical section in order to: // - Check for unprocessed jobs; // - Remove jobs from the processor queue; // - Do JobsProcessed work (such as removing jobs from the BackgroundParser's unprocessed list). CriticalSection *pcs = static_cast(bgp->Processor())->GetCriticalSection(); pcs->Enter(); for (;;) { // Grab a job (in lock) item = bgp->GetNextUnprocessedItem(); if (item == nullptr) { break; } bgp->Processor()->RemoveJob(item); pcs->Leave(); // Process job (if there is one) (outside lock) bool succeeded = bgp->Process(item, this, pse); pcs->Enter(); bgp->JobProcessed(item, succeeded); } pcs->Leave(); // Wait for the background threads to finish jobs they're already processing (if any). // TODO: Replace with a proper semaphore. while (*bgp->GetPendingBackgroundItemsPtr()); } Assert(!*bgp->GetPendingBackgroundItemsPtr()); // Restore parser state. this->m_pstmtCur = pstmtSave; this->m_isInBackground = false; this->m_nextBlockId = blockIdSave; *this->m_nextFunctionId = functionIdSave; } void Parser::FinishBackgroundPidRefs(BackgroundParseItem *item, bool isOtherParser) { for (BlockInfoStack *blockInfo = item->GetParseContext()->currentBlockInfo; blockInfo; blockInfo = blockInfo->pBlockInfoOuter) { if (isOtherParser) { this->BindPidRefs(blockInfo, item->GetMaxBlockId()); } else { this->BindPidRefs(blockInfo, item->GetMaxBlockId()); } } } void Parser::FinishBackgroundRegExpNodes() { // We have a list of RegExp nodes that we saw on the UI thread in functions we're parallel parsing, // and for each background job we have a list of RegExp nodes for which we couldn't allocate patterns. // We need to copy the pattern pointers from the UI thread nodes to the corresponding nodes on the // background nodes. // There may be UI thread nodes for which there are no background thread equivalents, because the UI thread // has to assume that the background thread won't defer anything. // Note that because these lists (and the list of background jobs) are SList's built by prepending, they are // all in reverse lexical order. Assert(!this->IsBackgroundParser()); Assert(this->fastScannedRegExpNodes); Assert(this->backgroundParseItems != nullptr); BackgroundParseItem *currBackgroundItem; #if DBG for (currBackgroundItem = this->backgroundParseItems; currBackgroundItem; currBackgroundItem = currBackgroundItem->GetNext()) { if (currBackgroundItem->RegExpNodeList()) { FOREACH_DLIST_ENTRY(ParseNodePtr, ArenaAllocator, pnode, currBackgroundItem->RegExpNodeList()) { Assert(pnode->AsParseNodeRegExp()->regexPattern == nullptr); } NEXT_DLIST_ENTRY; } } #endif // Hook up the patterns allocated on the main thread to the nodes created on the background thread. // Walk the list of foreground nodes, advancing through the work items and looking up each item. // Note that the background thread may have chosen to defer a given RegEx literal, so not every foreground // node will have a matching background node. Doesn't matter for correctness. // (It's inefficient, of course, to have to restart the inner loop from the beginning of the work item's // list, but it should be unusual to have many RegExes in a single work item's chunk of code. Figure out how // to start the inner loop from a known internal node within the list if that turns out to be important.) currBackgroundItem = this->backgroundParseItems; FOREACH_DLIST_ENTRY(ParseNodePtr, ArenaAllocator, pnodeFgnd, this->fastScannedRegExpNodes) { Assert(pnodeFgnd->nop == knopRegExp); Assert(pnodeFgnd->AsParseNodeRegExp()->regexPattern != nullptr); bool quit = false; while (!quit) { // Find the next work item with a RegEx in it. while (currBackgroundItem && currBackgroundItem->RegExpNodeList() == nullptr) { currBackgroundItem = currBackgroundItem->GetNext(); } if (!currBackgroundItem) { break; } // Walk the RegExps in the work item. FOREACH_DLIST_ENTRY(ParseNodePtr, ArenaAllocator, pnodeBgnd, currBackgroundItem->RegExpNodeList()) { Assert(pnodeBgnd->nop == knopRegExp); if (pnodeFgnd->ichMin <= pnodeBgnd->ichMin) { // Either we found a match, or the next background node is past the foreground node. // In any case, we can stop searching. if (pnodeFgnd->ichMin == pnodeBgnd->ichMin) { Assert(pnodeFgnd->ichLim == pnodeBgnd->ichLim); pnodeBgnd->AsParseNodeRegExp()->regexPattern = pnodeFgnd->AsParseNodeRegExp()->regexPattern; } quit = true; break; } } NEXT_DLIST_ENTRY; if (!quit) { // Need to advance to the next work item. currBackgroundItem = currBackgroundItem->GetNext(); } } } NEXT_DLIST_ENTRY; #if DBG for (currBackgroundItem = this->backgroundParseItems; currBackgroundItem; currBackgroundItem = currBackgroundItem->GetNext()) { if (currBackgroundItem->RegExpNodeList()) { FOREACH_DLIST_ENTRY(ParseNodePtr, ArenaAllocator, pnode, currBackgroundItem->RegExpNodeList()) { Assert(pnode->AsParseNodeRegExp()->regexPattern != nullptr); } NEXT_DLIST_ENTRY; } } #endif } #endif LabelId* Parser::CreateLabelId(IdentPtr pid) { LabelId* pLabelId; pLabelId = (LabelId*)m_nodeAllocator.Alloc(sizeof(LabelId)); if (NULL == pLabelId) Error(ERRnoMemory); pLabelId->pid = pid; pLabelId->next = NULL; return pLabelId; } /***************************************************************************** The following set of routines allocate parse tree nodes of various kinds. They catch an exception on out of memory. *****************************************************************************/ void Parser::AddAstSize(int size) { Assert(!this->m_deferringAST); Assert(m_pCurrentAstSize != NULL); *m_pCurrentAstSize += size; } void Parser::AddAstSizeAllowDefer(int size) { if (!this->m_deferringAST) { AddAstSize(size); } } // StaticCreate ParseNodeVar * Parser::StaticCreateTempNode(ParseNode* initExpr, ArenaAllocator * alloc) { ParseNodeVar * pnode = Anew(alloc, ParseNodeVar, knopTemp, 0, 0, nullptr); pnode->pnodeInit = initExpr; return pnode; } ParseNodeUni * Parser::StaticCreateTempRef(ParseNode* tempNode, ArenaAllocator * alloc) { return Anew(alloc, ParseNodeUni, knopTempRef, 0, 0, tempNode); } // Create Node with limit template typename OpCodeTrait::ParseNodeType * Parser::CreateNodeForOpT(charcount_t ichMin, charcount_t ichLim) { Assert(!this->m_deferringAST); typename OpCodeTrait::ParseNodeType * pnode = StaticCreateNodeT(&m_nodeAllocator, ichMin, ichLim); AddAstSize(sizeof(typename OpCodeTrait::ParseNodeType)); return pnode; } template typename OpCodeTrait::ParseNodeType * Parser::CreateAllowDeferNodeForOpT(charcount_t ichMin, charcount_t ichLim) { CompileAssert(OpCodeTrait::AllowDefer); typename OpCodeTrait::ParseNodeType * pnode = StaticCreateNodeT(&m_nodeAllocator, ichMin, ichLim); AddAstSizeAllowDefer(sizeof(typename OpCodeTrait::ParseNodeType)); return pnode; } #if DBG static const int g_mpnopcbNode[] = { #define PTNODE(nop,sn,pc,nk,ok,json) sizeof(ParseNode##nk), #include "ptlist.h" }; void VerifyNodeSize(OpCode nop, int size) { Assert(nop >= 0 && nop < knopLim); __analysis_assume(nop < knopLim); Assert(g_mpnopcbNode[nop] == size); } #endif // Create ParseNodeUni ParseNodeUni * Parser::CreateUniNode(OpCode nop, ParseNodePtr pnode1) { charcount_t ichMin; charcount_t ichLim; if (nullptr == pnode1) { // no ops ichMin = this->GetScanner()->IchMinTok(); ichLim = this->GetScanner()->IchLimTok(); } else { // 1 op ichMin = pnode1->ichMin; ichLim = pnode1->ichLim; this->CheckArguments(pnode1); } return CreateUniNode(nop, pnode1, ichMin, ichLim); } ParseNodeUni * Parser::CreateUniNode(OpCode nop, ParseNodePtr pnode1, charcount_t ichMin, charcount_t ichLim) { Assert(!this->m_deferringAST); DebugOnly(VerifyNodeSize(nop, sizeof(ParseNodeUni))); ParseNodeUni * pnode = Anew(&m_nodeAllocator, ParseNodeUni, nop, ichMin, ichLim, pnode1); AddAstSize(sizeof(ParseNodeUni)); return pnode; } // Create ParseNodeBin ParseNodeBin * Parser::StaticCreateBinNode(OpCode nop, ParseNodePtr pnode1, ParseNodePtr pnode2, ArenaAllocator* alloc, charcount_t ichMin, charcount_t ichLim) { DebugOnly(VerifyNodeSize(nop, sizeof(ParseNodeBin))); return Anew(alloc, ParseNodeBin, nop, ichMin, ichLim, pnode1, pnode2); } ParseNodeBin * Parser::CreateBinNode(OpCode nop, ParseNodePtr pnode1, ParseNodePtr pnode2) { Assert(!this->m_deferringAST); charcount_t ichMin; charcount_t ichLim; if (nullptr == pnode1) { // no ops Assert(nullptr == pnode2); ichMin = this->GetScanner()->IchMinTok(); ichLim = this->GetScanner()->IchLimTok(); } else { if (nullptr == pnode2) { // 1 op ichMin = pnode1->ichMin; ichLim = pnode1->ichLim; } else { // 2 ops ichMin = pnode1->ichMin; ichLim = pnode2->ichLim; if (nop != knopDot && nop != knopIndex) { this->CheckArguments(pnode2); } } if (nop != knopDot && nop != knopIndex) { this->CheckArguments(pnode1); } } return CreateBinNode(nop, pnode1, pnode2, ichMin, ichLim); } ParseNodeBin * Parser::CreateBinNode(OpCode nop, ParseNodePtr pnode1, ParseNodePtr pnode2, charcount_t ichMin, charcount_t ichLim) { Assert(!this->m_deferringAST); ParseNodeBin * pnode = StaticCreateBinNode(nop, pnode1, pnode2, &m_nodeAllocator, ichMin, ichLim); AddAstSize(sizeof(ParseNodeBin)); return pnode; } // Create ParseNodeTri ParseNodeTri * Parser::CreateTriNode(OpCode nop, ParseNodePtr pnode1, ParseNodePtr pnode2, ParseNodePtr pnode3) { charcount_t ichMin; charcount_t ichLim; if (nullptr == pnode1) { // no ops Assert(nullptr == pnode2); Assert(nullptr == pnode3); ichMin = this->GetScanner()->IchMinTok(); ichLim = this->GetScanner()->IchLimTok(); } else if (nullptr == pnode2) { // 1 op Assert(nullptr == pnode3); ichMin = pnode1->ichMin; ichLim = pnode1->ichLim; } else if (nullptr == pnode3) { // 2 op ichMin = pnode1->ichMin; ichLim = pnode2->ichLim; } else { // 3 ops ichMin = pnode1->ichMin; ichLim = pnode3->ichLim; } return CreateTriNode(nop, pnode1, pnode2, pnode3, ichMin, ichLim); } ParseNodeTri * Parser::CreateTriNode(OpCode nop, ParseNodePtr pnode1, ParseNodePtr pnode2, ParseNodePtr pnode3, charcount_t ichMin, charcount_t ichLim) { Assert(!this->m_deferringAST); DebugOnly(VerifyNodeSize(nop, sizeof(ParseNodeTri))); ParseNodeTri * pnode = Anew(&m_nodeAllocator, ParseNodeTri, nop, ichMin, ichLim); AddAstSize(sizeof(ParseNodeTri)); pnode->pnode1 = pnode1; pnode->pnode2 = pnode2; pnode->pnode3 = pnode3; return pnode; } // Create ParseNodeBlock ParseNodeBlock * Parser::StaticCreateBlockNode(ArenaAllocator* alloc, charcount_t ichMin, charcount_t ichLim, int blockId, PnodeBlockType blockType) { return Anew(alloc, ParseNodeBlock, ichMin, ichLim, blockId, blockType); } ParseNodeBlock * Parser::CreateBlockNode(PnodeBlockType blockType) { return CreateBlockNode(this->GetScanner()->IchMinTok(), this->GetScanner()->IchLimTok(), blockType); } ParseNodeBlock * Parser::CreateBlockNode(charcount_t ichMin, charcount_t ichLim, PnodeBlockType blockType) { Assert(OpCodeTrait::AllowDefer); ParseNodeBlock * pnode = StaticCreateBlockNode(&m_nodeAllocator, ichMin, ichLim, this->m_nextBlockId++, blockType); AddAstSizeAllowDefer(sizeof(ParseNodeBlock)); return pnode; } // Create ParseNodeVar ParseNodeVar * Parser::CreateDeclNode(OpCode nop, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl) { Assert(nop == knopVarDecl || nop == knopLetDecl || nop == knopConstDecl); ParseNodeVar * pnode = Anew(&m_nodeAllocator, ParseNodeVar, nop, this->GetScanner()->IchMinTok(), this->GetScanner()->IchLimTok(), pid); if (symbolType != STUnknown) { pnode->sym = AddDeclForPid(pnode, pid, symbolType, errorOnRedecl); } return pnode; } ParseNodeInt * Parser::CreateIntNode(int32 lw) { Assert(!this->m_deferringAST); ParseNodeInt * pnode = Anew(&m_nodeAllocator, ParseNodeInt, this->GetScanner()->IchMinTok(), this->GetScanner()->IchLimTok(), lw); AddAstSize(sizeof(ParseNodeInt)); return pnode; } ParseNodeStr * Parser::CreateStrNode(IdentPtr pid) { Assert(!this->m_deferringAST); ParseNodeStr * pnode = Anew(&m_nodeAllocator, ParseNodeStr, this->GetScanner()->IchMinTok(), this->GetScanner()->IchLimTok(), pid); pnode->grfpn |= PNodeFlags::fpnCanFlattenConcatExpr; AddAstSize(sizeof(ParseNodeStr)); return pnode; } ParseNodeName * Parser::CreateNameNode(IdentPtr pid) { ParseNodeName * pnode = Anew(&m_nodeAllocator, ParseNodeName, this->GetScanner()->IchMinTok(), this->GetScanner()->IchLimTok(), pid); AddAstSizeAllowDefer(sizeof(ParseNodeName)); return pnode; } ParseNodeName * Parser::CreateNameNode(IdentPtr pid, PidRefStack * ref, charcount_t ichMin, charcount_t ichLim) { ParseNodeName * pnode = Anew(&m_nodeAllocator, ParseNodeName, ichMin, ichLim, pid); pnode->SetSymRef(ref); AddAstSize(sizeof(ParseNodeName)); return pnode; } ParseNodeSpecialName * Parser::CreateSpecialNameNode(IdentPtr pid, PidRefStack * ref, charcount_t ichMin, charcount_t ichLim) { Assert(!this->m_deferringAST); ParseNodeSpecialName * pnode = Anew(&m_nodeAllocator, ParseNodeSpecialName, ichMin, ichLim, pid); pnode->SetSymRef(ref); if (pid == wellKnownPropertyPids._this) { pnode->isThis = true; } else if (pid == wellKnownPropertyPids._super || pid == wellKnownPropertyPids._superConstructor) { pnode->isSuper = true; } AddAstSize(sizeof(ParseNodeSpecialName)); return pnode; } ParseNodeSuperReference * Parser::CreateSuperReferenceNode(OpCode nop, ParseNodeSpecialName * pnode1, ParseNodePtr pnode2) { Assert(!this->m_deferringAST); Assert(pnode1 && pnode1->isSuper); Assert(pnode2 != nullptr); Assert(nop == knopDot || nop == knopIndex); ParseNodeSuperReference * pnode = Anew(&m_nodeAllocator, ParseNodeSuperReference, nop, pnode1->ichMin, pnode2->ichLim, pnode1, pnode2); AddAstSize(sizeof(ParseNodeSuperReference)); return pnode; } ParseNodeProg * Parser::CreateProgNode(bool isModuleSource, ULONG lineNumber) { ParseNodeProg * pnodeProg; if (isModuleSource) { pnodeProg = CreateNodeForOpT(); // knopModule is not actually handled anywhere since we would need to handle it everywhere we could // have knopProg and it would be treated exactly the same except for import/export statements. // We are only using it as a way to get the correct size for PnModule. // Consider: Should we add a flag to PnProg which is false but set to true in PnModule? // If we do, it can't be a virtual method since the parse nodes are all in a union. pnodeProg->nop = knopProg; } else { pnodeProg = CreateNodeForOpT(); } pnodeProg->cbMin = this->GetScanner()->IecpMinTok(); pnodeProg->cbStringMin = pnodeProg->cbMin; pnodeProg->lineNumber = lineNumber; pnodeProg->homeObjLocation = Js::Constants::NoRegister; pnodeProg->superRestrictionState = SuperRestrictionState::Disallowed; return pnodeProg; } ParseNodeCall * Parser::CreateCallNode(OpCode nop, ParseNodePtr pnode1, ParseNodePtr pnode2) { charcount_t ichMin; charcount_t ichLim; if (nullptr == pnode1) { Assert(nullptr == pnode2); ichMin = this->GetScanner()->IchMinTok(); ichLim = this->GetScanner()->IchLimTok(); } else { ichMin = pnode1->ichMin; ichLim = pnode2 == nullptr ? pnode1->ichLim : pnode2->ichLim; if (pnode1->nop == knopDot || pnode1->nop == knopIndex) { this->CheckArguments(pnode1->AsParseNodeBin()->pnode1); } } return CreateCallNode(nop, pnode1, pnode2, ichMin, ichLim); } ParseNodeCall * Parser::CreateCallNode(OpCode nop, ParseNodePtr pnode1, ParseNodePtr pnode2, charcount_t ichMin, charcount_t ichLim) { Assert(!this->m_deferringAST); // Classes, derived from ParseNodeCall, can be created here as well, // as long as their size matches kcbPnCall (that is, they don't add // any data members of their own). DebugOnly(VerifyNodeSize(nop, sizeof(ParseNodeCall))); ParseNodeCall* pnode = Anew(&m_nodeAllocator, ParseNodeCall, nop, ichMin, ichLim, pnode1, pnode2); AddAstSize(sizeof(ParseNodeCall)); return pnode; } ParseNodeSuperCall * Parser::CreateSuperCallNode(ParseNodeSpecialName * pnode1, ParseNodePtr pnode2) { Assert(!this->m_deferringAST); Assert(pnode1 && pnode1->isSuper); ParseNodeSuperCall* pnode = Anew(&m_nodeAllocator, ParseNodeSuperCall, knopCall, pnode1->ichMin, pnode2 == nullptr ? pnode1->ichLim : pnode2->ichLim, pnode1, pnode2); AddAstSize(sizeof(ParseNodeSuperCall)); return pnode; } ParseNodeParamPattern * Parser::CreateParamPatternNode(ParseNode * pnode1) { ParseNodeParamPattern * paramPatternNode = CreateNodeForOpT(pnode1->ichMin, pnode1->ichLim); paramPatternNode->pnode1 = pnode1; paramPatternNode->pnodeNext = nullptr; paramPatternNode->location = Js::Constants::NoRegister; return paramPatternNode; } ParseNodeParamPattern * Parser::CreateDummyParamPatternNode(charcount_t ichMin) { ParseNodeParamPattern * paramPatternNode = CreateNodeForOpT(ichMin); paramPatternNode->pnode1 = nullptr; paramPatternNode->pnodeNext = nullptr; paramPatternNode->location = Js::Constants::NoRegister; return paramPatternNode; } ParseNodeObjLit * Parser::CreateObjectPatternNode(ParseNodePtr pnodeMemberList, charcount_t ichMin, charcount_t ichLim, bool convertToPattern) { // Count the number of non-rest members in the object uint32 staticCount = 0; uint32 computedCount = 0; bool hasRest = false; ParseNodePtr pnodeMemberNodeList = convertToPattern ? nullptr : pnodeMemberList; if (pnodeMemberList != nullptr) { Assert(pnodeMemberList->nop == knopList || (!convertToPattern && pnodeMemberList->nop == knopObjectPatternMember) || convertToPattern || pnodeMemberList->nop == knopEllipsis); ForEachItemInList(pnodeMemberList, [&](ParseNodePtr item) { ParseNodePtr memberNode = convertToPattern ? ConvertMemberToMemberPattern(item) : item; if (convertToPattern) { AppendToList(&pnodeMemberNodeList, memberNode); } if (memberNode->nop != knopEllipsis) { ParseNodePtr nameNode = memberNode->AsParseNodeBin()->pnode1; Assert(nameNode->nop == knopComputedName || nameNode->nop == knopStr); if (nameNode->nop == knopComputedName) { computedCount++; } else { staticCount++; } } else { hasRest = true; } }); } ParseNodeObjLit * objectPatternNode = CreateNodeForOpT(ichMin, ichLim); objectPatternNode->pnode1 = pnodeMemberNodeList; objectPatternNode->computedCount = computedCount; objectPatternNode->staticCount = staticCount; objectPatternNode->hasRest = hasRest; return objectPatternNode; } Symbol* Parser::AddDeclForPid(ParseNodeVar * pnodeVar, IdentPtr pid, SymbolType symbolType, bool errorOnRedecl) { Assert(pnodeVar->IsVarLetOrConst()); PidRefStack *refForUse = nullptr, *refForDecl = nullptr; BlockInfoStack *blockInfo; bool fBlockScope = false; if (pnodeVar->nop != knopVarDecl || symbolType == STFunction) { Assert(m_pstmtCur); if (m_pstmtCur->GetNop() != knopBlock) { // Let/const declared in a bare statement context. Error(ERRDeclOutOfStmt); } if (m_pstmtCur->pstmtOuter && m_pstmtCur->pstmtOuter->GetNop() == knopSwitch) { // Let/const declared inside a switch block (requiring conservative use-before-decl check). pnodeVar->isSwitchStmtDecl = true; } fBlockScope = pnodeVar->nop != knopVarDecl || ( !GetCurrentBlockInfo()->pnodeBlock->scope || GetCurrentBlockInfo()->pnodeBlock->scope->GetScopeType() != ScopeType_GlobalEvalBlock ); } if (fBlockScope) { blockInfo = GetCurrentBlockInfo(); } else { blockInfo = GetCurrentFunctionBlockInfo(); } refForDecl = this->FindOrAddPidRef(pid, blockInfo->pnodeBlock->blockId, GetCurrentFunctionNode()->functionId); if (refForDecl == nullptr) { Error(ERRnoMemory); } if (refForDecl->funcId != GetCurrentFunctionNode()->functionId) { // Fix up the function id, which is incorrect if we're reparsing lambda parameters Assert(this->m_reparsingLambdaParams); refForDecl->funcId = GetCurrentFunctionNode()->functionId; } if (blockInfo == GetCurrentBlockInfo()) { refForUse = refForDecl; } else { refForUse = this->PushPidRef(pid); } pnodeVar->symRef = refForUse->GetSymRef(); Symbol *sym = refForDecl->GetSym(); if (sym != nullptr) { // Multiple declarations in the same scope. 3 possibilities: error, existing one wins, new one wins. switch (pnodeVar->nop) { case knopLetDecl: case knopConstDecl: if (!sym->GetDecl()->AsParseNodeVar()->isBlockScopeFncDeclVar && !sym->IsArguments()) { // If the built-in arguments is shadowed then don't throw Assert(errorOnRedecl); // Redeclaration error. Error(ERRRedeclaration); } else { // (New) let/const hides the (old) var sym->SetSymbolType(symbolType); sym->SetDecl(pnodeVar); } break; case knopVarDecl: if (m_currentScope->GetScopeType() == ScopeType_Parameter && !sym->IsArguments()) { // If this is a parameter list, mark the scope to indicate that it has duplicate definition unless it is shadowing the default arguments symbol. // If later this turns out to be a non-simple param list (like function f(a, a, c = 1) {}) then it is a SyntaxError to have duplicate formals. m_currentScope->SetHasDuplicateFormals(); } if (sym->GetDecl() == nullptr) { sym->SetDecl(pnodeVar); break; } switch (sym->GetDecl()->nop) { case knopLetDecl: case knopConstDecl: // Destructuring made possible to have the formals to be the let bind. But that shouldn't throw the error. if (errorOnRedecl && (!IsES6DestructuringEnabled() || sym->GetSymbolType() != STFormal)) { Error(ERRRedeclaration); } // If !errorOnRedecl, (old) let/const hides the (new) var, so do nothing. break; case knopVarDecl: // Legal redeclaration. Who wins? if (errorOnRedecl || sym->GetDecl()->AsParseNodeVar()->isBlockScopeFncDeclVar || sym->IsArguments()) { if (symbolType == STFormal || (symbolType == STFunction && sym->GetSymbolType() != STFormal) || sym->GetSymbolType() == STVariable) { // New decl wins. sym->SetSymbolType(symbolType); sym->SetDecl(pnodeVar); } } break; } break; } } else { Scope *scope = blockInfo->pnodeBlock->scope; if (scope == nullptr) { Assert(blockInfo->pnodeBlock->blockType == PnodeBlockType::Regular); scope = Anew(&m_nodeAllocator, Scope, &m_nodeAllocator, ScopeType_Block); if (this->IsCurBlockInLoop()) { scope->SetIsBlockInLoop(); } blockInfo->pnodeBlock->scope = scope; PushScope(scope); } ParseNodeFnc * pnodeFnc = GetCurrentFunctionNode(); if (scope->GetScopeType() == ScopeType_GlobalEvalBlock) { Assert(fBlockScope); Assert(scope->GetEnclosingScope() == m_currentNodeProg->scope); // Check for same-named decl in Global scope. CheckRedeclarationErrorForBlockId(pid, 0); } else if (scope->GetScopeType() == ScopeType_Global && (this->m_grfscr & fscrEvalCode) && !(m_functionBody && m_functionBody->GetScopeInfo())) { // Check for same-named decl in GlobalEvalBlock scope. Note that this is not necessary // if we're compiling a deferred nested function and the global scope was restored from cached info, // because in that case we don't need a GlobalEvalScope. Assert(!fBlockScope || (this->m_grfscr & fscrConsoleScopeEval) == fscrConsoleScopeEval); CheckRedeclarationErrorForBlockId(pid, 1); } else if (!pnodeFnc->IsBodyAndParamScopeMerged() && scope->GetScopeType() == ScopeType_FunctionBody && (pnodeVar->nop == knopLetDecl || pnodeVar->nop == knopConstDecl)) { // In case of split scope function when we add a new let or const declaration to the body // we have to check whether the param scope already has the same symbol defined. CheckRedeclarationErrorForBlockId(pid, pnodeFnc->pnodeScopes->blockId); } if (!sym) { const char16 *name = reinterpret_cast(pid->Psz()); int nameLength = pid->Cch(); SymbolName const symName(name, nameLength); Assert(!scope->FindLocalSymbol(symName)); sym = Anew(&m_nodeAllocator, Symbol, symName, pnodeVar, symbolType); scope->AddNewSymbol(sym); sym->SetPid(pid); } refForDecl->SetSym(sym); } return sym; } void Parser::CheckRedeclarationErrorForBlockId(IdentPtr pid, int blockId) { // If the ref stack entry for the blockId contains a sym then throw redeclaration error PidRefStack *pidRefOld = pid->GetPidRefForScopeId(blockId); if (pidRefOld && pidRefOld->GetSym() && !pidRefOld->GetSym()->IsArguments()) { Error(ERRRedeclaration); } } bool Parser::IsCurBlockInLoop() const { for (StmtNest *stmt = this->m_pstmtCur; stmt != nullptr; stmt = stmt->pstmtOuter) { OpCode nop = stmt->GetNop(); if (ParseNode::Grfnop(nop) & fnopContinue) { return true; } if (nop == knopFncDecl) { return false; } } return false; } void Parser::RestorePidRefForSym(Symbol *sym) { IdentPtr pid = this->GetHashTbl()->PidHashNameLen(sym->GetName().GetBuffer(), sym->GetName().GetLength()); Assert(pid); sym->SetPid(pid); PidRefStack *ref = this->PushPidRef(pid); ref->SetSym(sym); } void Parser::CheckPidIsValid(IdentPtr pid, bool autoArgumentsObject) { if (IsStrictMode()) { // in strict mode, variable named 'eval' cannot be created if (pid == wellKnownPropertyPids.eval) { Error(ERREvalUsage); } else if (pid == wellKnownPropertyPids.arguments && !autoArgumentsObject) { Error(ERRArgsUsage); } } } // CreateVarDecl needs m_ppnodeVar to be pointing to the right function. // Post-parsing rewriting during bytecode gen may have m_ppnodeVar pointing to the last parsed function. // This function sets up m_ppnodeVar to point to the given pnodeFnc and creates the new var declaration. // This prevents accidentally adding var declarations to the last parsed function. ParseNodeVar * Parser::AddVarDeclNode(IdentPtr pid, ParseNodeFnc * pnodeFnc) { AnalysisAssert(pnodeFnc); ParseNodePtr *const ppnodeVarSave = m_ppnodeVar; m_ppnodeVar = &pnodeFnc->pnodeVars; while (*m_ppnodeVar != nullptr) { m_ppnodeVar = &(*m_ppnodeVar)->AsParseNodeVar()->pnodeNext; } ParseNodeVar * pnode = CreateVarDeclNode(pid, STUnknown, false, 0, /* checkReDecl = */ false); m_ppnodeVar = ppnodeVarSave; return pnode; } ParseNodeVar * Parser::CreateModuleImportDeclNode(IdentPtr localName) { ParseNodeVar * declNode = CreateBlockScopedDeclNode(localName, knopConstDecl); Symbol* sym = declNode->sym; sym->SetIsModuleExportStorage(true); sym->SetIsModuleImport(true); return declNode; } ParseNodeVar * Parser::CreateVarDeclNode(IdentPtr pid, SymbolType symbolType, bool autoArgumentsObject, ParseNodePtr pnodeFnc, bool errorOnRedecl) { ParseNodeVar * pnode = CreateDeclNode(knopVarDecl, pid, symbolType, errorOnRedecl); // Append the variable to the end of the current variable list. Assert(m_ppnodeVar); pnode->pnodeNext = *m_ppnodeVar; *m_ppnodeVar = pnode; if (nullptr != pid) { // this is not a temp - make sure temps go after this node Assert(pid); m_ppnodeVar = &pnode->pnodeNext; CheckPidIsValid(pid, autoArgumentsObject); } return pnode; } ParseNodeVar * Parser::CreateBlockScopedDeclNode(IdentPtr pid, OpCode nodeType) { Assert(nodeType == knopConstDecl || nodeType == knopLetDecl); ParseNodeVar * pnode = CreateDeclNode(nodeType, pid, STVariable, true); if (nullptr != pid) { Assert(pid); AddVarDeclToBlock(pnode); CheckPidIsValid(pid); } return pnode; } void Parser::AddVarDeclToBlock(ParseNodeVar *pnode) { Assert(pnode->nop == knopConstDecl || pnode->nop == knopLetDecl); // Maintain a combined list of let and const declarations to keep // track of declaration order. Assert(m_currentBlockInfo->m_ppnodeLex); *m_currentBlockInfo->m_ppnodeLex = pnode; m_currentBlockInfo->m_ppnodeLex = &pnode->pnodeNext; pnode->pnodeNext = nullptr; } void Parser::SetCurrentStatement(StmtNest *stmt) { m_pstmtCur = stmt; } template ParseNodeBlock * Parser::StartParseBlockWithCapacity(PnodeBlockType blockType, ScopeType scopeType, int capacity) { Scope *scope = nullptr; // Block scopes are not created lazily in the case where we're repopulating a persisted scope. scope = Anew(&m_nodeAllocator, Scope, &m_nodeAllocator, scopeType, capacity); PushScope(scope); return StartParseBlockHelper(blockType, scope, nullptr); } template ParseNodeBlock * Parser::StartParseBlock(PnodeBlockType blockType, ScopeType scopeType, LabelId* pLabelId) { Scope *scope = nullptr; // Block scopes are created lazily when we discover block-scoped content. if (scopeType != ScopeType_Unknown && scopeType != ScopeType_Block) { scope = Anew(&m_nodeAllocator, Scope, &m_nodeAllocator, scopeType); PushScope(scope); } return StartParseBlockHelper(blockType, scope, pLabelId); } template ParseNodeBlock * Parser::StartParseBlockHelper(PnodeBlockType blockType, Scope *scope, LabelId* pLabelId) { ParseNodeBlock * pnodeBlock = CreateBlockNode(blockType); pnodeBlock->scope = scope; BlockInfoStack *newBlockInfo = PushBlockInfo(pnodeBlock); PushStmt(&newBlockInfo->pstmt, pnodeBlock, knopBlock, pLabelId); return pnodeBlock; } void Parser::PushScope(Scope *scope) { Assert(scope); scope->SetEnclosingScope(m_currentScope); m_currentScope = scope; } void Parser::PopScope(Scope *scope) { Assert(scope == m_currentScope); m_currentScope = scope->GetEnclosingScope(); scope->SetEnclosingScope(nullptr); } void Parser::PushFuncBlockScope(ParseNodeBlock * pnodeBlock, ParseNodePtr **ppnodeScopeSave, ParseNodePtr **ppnodeExprScopeSave) { // Maintain the scope tree. pnodeBlock->pnodeScopes = nullptr; pnodeBlock->pnodeNext = nullptr; // Insert this block into the active list of scopes (m_ppnodeExprScope or m_ppnodeScope). // Save the current block's "next" pointer as the new endpoint of that list. if (m_ppnodeExprScope) { *ppnodeScopeSave = m_ppnodeScope; Assert(*m_ppnodeExprScope == nullptr); *m_ppnodeExprScope = pnodeBlock; *ppnodeExprScopeSave = &pnodeBlock->pnodeNext; } else { Assert(m_ppnodeScope); Assert(*m_ppnodeScope == nullptr); *m_ppnodeScope = pnodeBlock; *ppnodeScopeSave = &pnodeBlock->pnodeNext; *ppnodeExprScopeSave = m_ppnodeExprScope; } // Advance the global scope list pointer to the new block's child list. m_ppnodeScope = &pnodeBlock->pnodeScopes; // Set m_ppnodeExprScope to NULL to make that list inactive. m_ppnodeExprScope = nullptr; } void Parser::PopFuncBlockScope(ParseNodePtr *ppnodeScopeSave, ParseNodePtr *ppnodeExprScopeSave) { Assert(m_ppnodeExprScope == nullptr || *m_ppnodeExprScope == nullptr); m_ppnodeExprScope = ppnodeExprScopeSave; Assert(m_ppnodeScope); Assert(nullptr == *m_ppnodeScope); m_ppnodeScope = ppnodeScopeSave; } template ParseNodeBlock * Parser::ParseBlock(LabelId* pLabelId) { ParseNodeBlock * pnodeBlock = nullptr; ParseNodePtr *ppnodeScopeSave = nullptr; ParseNodePtr *ppnodeExprScopeSave = nullptr; pnodeBlock = StartParseBlock(PnodeBlockType::Regular, ScopeType_Block, pLabelId); BlockInfoStack* outerBlockInfo = m_currentBlockInfo->pBlockInfoOuter; if (outerBlockInfo != nullptr && outerBlockInfo->pnodeBlock != nullptr && outerBlockInfo->pnodeBlock->scope != nullptr && outerBlockInfo->pnodeBlock->scope->GetScopeType() == ScopeType_CatchParamPattern) { // If we are parsing the catch block then destructured params can have let declarations. Let's add them to the new block. for (ParseNodePtr pnode = m_currentBlockInfo->pBlockInfoOuter->pnodeBlock->pnodeLexVars; pnode; pnode = pnode->AsParseNodeVar()->pnodeNext) { PidRefStack* ref = PushPidRef(pnode->AsParseNodeVar()->sym->GetPid()); ref->SetSym(pnode->AsParseNodeVar()->sym); } } ChkCurTok(tkLCurly, ERRnoLcurly); ParseNodePtr * ppnodeList = nullptr; if (buildAST) { PushFuncBlockScope(pnodeBlock, &ppnodeScopeSave, &ppnodeExprScopeSave); ppnodeList = &pnodeBlock->pnodeStmt; } ParseStmtList(ppnodeList); if (buildAST) { PopFuncBlockScope(ppnodeScopeSave, ppnodeExprScopeSave); } FinishParseBlock(pnodeBlock); ChkCurTok(tkRCurly, ERRnoRcurly); return pnodeBlock; } bool Parser::IsSpecialName(IdentPtr pid) { return pid == wellKnownPropertyPids._this || pid == wellKnownPropertyPids._super || pid == wellKnownPropertyPids._superConstructor || pid == wellKnownPropertyPids._newTarget; } ParseNodeSpecialName * Parser::ReferenceSpecialName(IdentPtr pid, charcount_t ichMin, charcount_t ichLim, bool createNode) { PidRefStack* ref = this->PushPidRef(pid); if (!createNode) { return nullptr; } return CreateSpecialNameNode(pid, ref, ichMin, ichLim); } ParseNodeVar * Parser::CreateSpecialVarDeclIfNeeded(ParseNodeFnc * pnodeFnc, IdentPtr pid, bool forceCreate) { Assert(pid != nullptr); PidRefStack* ref = pid->GetTopRef(); // If the function has a reference to pid or we set forceCreate, make a special var decl if (forceCreate || (ref && ref->GetScopeId() >= m_currentBlockInfo->pnodeBlock->blockId)) { return this->CreateSpecialVarDeclNode(pnodeFnc, pid); } return nullptr; } void Parser::CreateSpecialSymbolDeclarations(ParseNodeFnc * pnodeFnc) { // Lambda function cannot have any special bindings. if (pnodeFnc->IsLambda()) { return; } bool isTopLevelEventHandler = (this->m_grfscr & fscrImplicitThis) && !pnodeFnc->IsNested(); // Create a 'this' symbol for non-lambda functions with references to 'this', and all class constructors and top level event hanlders. ParseNodePtr varDeclNode = CreateSpecialVarDeclIfNeeded(pnodeFnc, wellKnownPropertyPids._this, pnodeFnc->IsClassConstructor() || isTopLevelEventHandler); if (varDeclNode) { varDeclNode->AsParseNodeVar()->sym->SetIsThis(true); if (pnodeFnc->IsDerivedClassConstructor()) { varDeclNode->AsParseNodeVar()->sym->SetNeedDeclaration(true); } } // Create a 'new.target' symbol for any ordinary function with a reference and all class constructors. varDeclNode = CreateSpecialVarDeclIfNeeded(pnodeFnc, wellKnownPropertyPids._newTarget, pnodeFnc->IsClassConstructor()); if (varDeclNode) { varDeclNode->AsParseNodeVar()->sym->SetIsNewTarget(true); } // Create a 'super' (as a reference) symbol. varDeclNode = CreateSpecialVarDeclIfNeeded(pnodeFnc, wellKnownPropertyPids._super); if (varDeclNode) { varDeclNode->AsParseNodeVar()->sym->SetIsSuper(true); } // Create a 'super' (as the call target for super()) symbol only for derived class constructors. varDeclNode = CreateSpecialVarDeclIfNeeded(pnodeFnc, wellKnownPropertyPids._superConstructor); if (varDeclNode) { varDeclNode->AsParseNodeVar()->sym->SetIsSuperConstructor(true); } } void Parser::FinishParseBlock(ParseNodeBlock *pnodeBlock, bool needScanRCurly) { Assert(m_currentBlockInfo != nullptr && pnodeBlock == m_currentBlockInfo->pnodeBlock); if (needScanRCurly) { // Only update the ichLim if we were expecting an RCurly. If there is an // expression body without a necessary RCurly, the correct ichLim will // have been set already. pnodeBlock->ichLim = this->GetScanner()->IchLimTok(); } BindPidRefs(GetCurrentBlockInfo(), m_nextBlockId - 1); PopStmt(&m_currentBlockInfo->pstmt); PopBlockInfo(); Scope *scope = pnodeBlock->scope; if (scope) { PopScope(scope); } } void Parser::FinishParseFncExprScope(ParseNodeFnc * pnodeFnc, ParseNodeBlock * pnodeFncExprScope) { int fncExprScopeId = pnodeFncExprScope->blockId; ParseNodePtr pnodeName = pnodeFnc->pnodeName; if (pnodeName) { Assert(pnodeName->nop == knopVarDecl); BindPidRefsInScope(pnodeName->AsParseNodeVar()->pid, pnodeName->AsParseNodeVar()->sym, fncExprScopeId, m_nextBlockId - 1); } FinishParseBlock(pnodeFncExprScope); } template void Parser::BindPidRefs(BlockInfoStack *blockInfo, uint maxBlockId) { // We need to bind all assignments in order to emit assignment to 'const' error int blockId = blockInfo->pnodeBlock->blockId; Scope *scope = blockInfo->pnodeBlock->scope; if (scope) { auto bindPidRefs = [blockId, maxBlockId, this](Symbol *sym) { ParseNodePtr pnode = sym->GetDecl(); IdentPtr pid; #if PROFILE_DICTIONARY int depth = 0; #endif Assert(pnode); switch (pnode->nop) { case knopVarDecl: case knopLetDecl: case knopConstDecl: pid = pnode->AsParseNodeVar()->pid; if (backgroundPidRef) { pid = this->GetHashTbl()->FindExistingPid(pid->Psz(), pid->Psz() + pid->Cch(), pid->Cch(), pid->Hash(), nullptr, nullptr #if PROFILE_DICTIONARY , depth #endif ); if (pid == nullptr) { break; } } this->BindPidRefsInScope(pid, sym, blockId, maxBlockId); break; case knopName: pid = pnode->AsParseNodeName()->pid; if (backgroundPidRef) { pid = this->GetHashTbl()->FindExistingPid(pid->Psz(), pid->Psz() + pid->Cch(), pid->Cch(), pid->Hash(), nullptr, nullptr #if PROFILE_DICTIONARY , depth #endif ); if (pid == nullptr) { break; } } this->BindPidRefsInScope(pid, sym, blockId, maxBlockId); break; default: Assert(0); break; } }; scope->ForEachSymbol(bindPidRefs); } } void Parser::BindPidRefsInScope(IdentPtr pid, Symbol *sym, int blockId, uint maxBlockId) { PidRefStack *ref, *nextRef, *lastRef = nullptr; Js::LocalFunctionId funcId = GetCurrentFunctionNode()->functionId; Assert(sym); if (pid->GetIsModuleExport() && IsTopLevelModuleFunc()) { sym->SetIsModuleExportStorage(true); } bool hasFuncAssignment = sym->GetHasFuncAssignment(); bool doesEscape = false; for (ref = pid->GetTopRef(); ref && ref->GetScopeId() >= blockId; ref = nextRef) { // Fix up sym* on PID ref. Assert(!ref->GetSym() || ref->GetSym() == sym); nextRef = ref->prev; Assert(ref->GetScopeId() >= 0); if ((uint)ref->GetScopeId() > maxBlockId) { lastRef = ref; continue; } ref->SetSym(sym); this->RemovePrevPidRef(pid, lastRef); if (ref->IsUsedInLdElem()) { sym->SetIsUsedInLdElem(true); } if (ref->IsAssignment()) { sym->PromoteAssignmentState(); if (sym->GetIsFormal()) { GetCurrentFunctionNode()->SetHasAnyWriteToFormals(true); } } if (ref->GetFuncScopeId() != funcId && !sym->GetIsGlobal() && !sym->GetIsModuleExportStorage()) { Assert(ref->GetFuncScopeId() > funcId); sym->SetHasNonLocalReference(); if (ref->IsDynamicBinding()) { sym->SetNeedsScopeObject(); } } if (ref->IsFuncAssignment()) { hasFuncAssignment = true; } if (ref->IsEscape()) { doesEscape = true; } if (m_currentNodeFunc && doesEscape && hasFuncAssignment) { if (m_sourceContextInfo ? !PHASE_OFF_RAW(Js::DisableStackFuncOnDeferredEscapePhase, m_sourceContextInfo->sourceContextId, m_currentNodeFunc->functionId) : !PHASE_OFF1(Js::DisableStackFuncOnDeferredEscapePhase)) { m_currentNodeFunc->SetNestedFuncEscapes(); } } if (ref->GetScopeId() == blockId) { break; } } } void Parser::MarkEscapingRef(ParseNodePtr pnode, IdentToken *pToken) { if (m_currentNodeFunc == nullptr) { return; } if (pnode && pnode->nop == knopFncDecl) { this->SetNestedFuncEscapes(); } else if (pToken->pid) { PidRefStack *pidRef = pToken->pid->GetTopRef(); if (pidRef->sym) { if (pidRef->sym->GetSymbolType() == STFunction) { this->SetNestedFuncEscapes(); } } else { pidRef->isEscape = true; } } } void Parser::SetNestedFuncEscapes() const { if (m_sourceContextInfo ? !PHASE_OFF_RAW(Js::DisableStackFuncOnDeferredEscapePhase, m_sourceContextInfo->sourceContextId, m_currentNodeFunc->functionId) : !PHASE_OFF1(Js::DisableStackFuncOnDeferredEscapePhase)) { m_currentNodeFunc->SetNestedFuncEscapes(); } } void Parser::PopStmt(StmtNest *pStmt) { Assert(pStmt == m_pstmtCur); SetCurrentStatement(m_pstmtCur->pstmtOuter); } BlockInfoStack *Parser::PushBlockInfo(ParseNodeBlock * pnodeBlock) { BlockInfoStack *newBlockInfo = (BlockInfoStack *)m_nodeAllocator.Alloc(sizeof(BlockInfoStack)); Assert(nullptr != newBlockInfo); newBlockInfo->pnodeBlock = pnodeBlock; newBlockInfo->pBlockInfoOuter = m_currentBlockInfo; newBlockInfo->m_ppnodeLex = &pnodeBlock->pnodeLexVars; if (pnodeBlock->blockType != PnodeBlockType::Regular) { newBlockInfo->pBlockInfoFunction = newBlockInfo; } else { Assert(m_currentBlockInfo); newBlockInfo->pBlockInfoFunction = m_currentBlockInfo->pBlockInfoFunction; } m_currentBlockInfo = newBlockInfo; return newBlockInfo; } void Parser::PopBlockInfo() { Assert(m_currentBlockInfo); PopDynamicBlock(); m_currentBlockInfo = m_currentBlockInfo->pBlockInfoOuter; } void Parser::PushDynamicBlock() { Assert(GetCurrentBlock()); int blockId = GetCurrentBlock()->blockId; if (m_currentDynamicBlock && m_currentDynamicBlock->id == blockId) { return; } BlockIdsStack *info = (BlockIdsStack *)m_nodeAllocator.Alloc(sizeof(BlockIdsStack)); if (nullptr == info) { Error(ERRnoMemory); } info->id = blockId; info->prev = m_currentDynamicBlock; m_currentDynamicBlock = info; } void Parser::PopDynamicBlock() { int blockId = GetCurrentDynamicBlockId(); if (GetCurrentBlock()->blockId != blockId || blockId == -1) { return; } Assert(m_currentDynamicBlock); this->GetHashTbl()->VisitPids([&](IdentPtr pid) { for (PidRefStack *ref = pid->GetTopRef(); ref && ref->GetScopeId() >= blockId; ref = ref->prev) { ref->SetDynamicBinding(); } }); m_currentDynamicBlock = m_currentDynamicBlock->prev; } int Parser::GetCurrentDynamicBlockId() const { return m_currentDynamicBlock ? m_currentDynamicBlock->id : -1; } ParseNodeFnc *Parser::GetCurrentFunctionNode() { if (m_currentNodeDeferredFunc != nullptr) { return m_currentNodeDeferredFunc; } else if (m_currentNodeFunc != nullptr) { return m_currentNodeFunc; } else { AssertMsg(GetFunctionBlock()->blockType == PnodeBlockType::Global, "Most likely we are trying to find a syntax error, related to 'let' or 'const' in deferred parsing mode with disabled support of 'let' and 'const'"); return m_currentNodeProg; } } ParseNodeFnc *Parser::GetCurrentNonLambdaFunctionNode() { if (m_currentNodeNonLambdaDeferredFunc != nullptr) { return m_currentNodeNonLambdaDeferredFunc; } return m_currentNodeNonLambdaFunc; } void Parser::RegisterRegexPattern(UnifiedRegex::RegexPattern *const regexPattern) { Assert(regexPattern); // ensure a no-throw add behavior here, to catch out of memory exceptions, using the guest arena allocator if (!m_registeredRegexPatterns.PrependNoThrow(m_tempGuestArena->GetAllocator(), regexPattern)) { Parser::Error(ERRnoMemory); } } void Parser::CaptureState(ParserState *state) { Assert(state != nullptr); state->m_funcInArraySave = m_funcInArray; state->m_funcInArrayDepthSave = m_funcInArrayDepth; state->m_nestedCountSave = *m_pnestedCount; 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); #if DEBUG state->m_currentBlockInfo = m_currentBlockInfo; #endif } void Parser::RestoreStateFrom(ParserState *state) { Assert(state != nullptr); Assert(state->m_currentBlockInfo == m_currentBlockInfo); m_funcInArray = state->m_funcInArraySave; 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) { *state->m_ppnodeScopeSave = nullptr; } if (state->m_ppnodeExprScopeSave != nullptr) { *state->m_ppnodeExprScopeSave = nullptr; } m_ppnodeScope = state->m_ppnodeScopeSave; m_ppnodeExprScope = state->m_ppnodeExprScopeSave; } void Parser::AddToNodeListEscapedUse(ParseNode ** ppnodeList, ParseNode *** pppnodeLast, ParseNode * pnodeAdd) { AddToNodeList(ppnodeList, pppnodeLast, pnodeAdd); pnodeAdd->SetIsInList(); } void Parser::AddToNodeList(ParseNode ** ppnodeList, ParseNode *** pppnodeLast, ParseNode * pnodeAdd) { Assert(!this->m_deferringAST); if (nullptr == *pppnodeLast) { // should be an empty list Assert(nullptr == *ppnodeList); *ppnodeList = pnodeAdd; *pppnodeLast = ppnodeList; } else { // Assert(*ppnodeList); Assert(**pppnodeLast); ParseNode *pnodeT = CreateBinNode(knopList, **pppnodeLast, pnodeAdd); **pppnodeLast = pnodeT; *pppnodeLast = &pnodeT->AsParseNodeBin()->pnode2; } } // Check reference to "arguments" that indicates the object may escape. void Parser::CheckArguments(ParseNodePtr pnode) { if (m_currentNodeFunc && this->NodeIsIdent(pnode, wellKnownPropertyPids.arguments)) { m_currentNodeFunc->SetHasHeapArguments(); } } // Check use of "arguments" that requires instantiation of the object. void Parser::CheckArgumentsUse(IdentPtr pid, ParseNodeFnc * pnodeFnc) { if (pid == wellKnownPropertyPids.arguments) { if (pnodeFnc != nullptr && pnodeFnc != m_currentNodeProg) { pnodeFnc->SetUsesArguments(TRUE); } else { m_UsesArgumentsAtGlobal = true; } } } void Parser::CheckStrictModeEvalArgumentsUsage(IdentPtr pid, ParseNodePtr pnode) { if (pid != nullptr) { // In strict mode, 'eval' / 'arguments' cannot be assigned to. if (pid == wellKnownPropertyPids.eval) { Error(ERREvalUsage, pnode); } if (pid == wellKnownPropertyPids.arguments) { Error(ERRArgsUsage, pnode); } } } void Parser::ReduceDeferredScriptLength(size_t chars) { // If we're in deferred mode, subtract the given char count from the total length, // and see if this puts us under the deferral threshold. if (((m_grfscr & (fscrCanDeferFncParse | fscrWillDeferFncParse)) == (fscrCanDeferFncParse | fscrWillDeferFncParse)) && ( PHASE_OFF1(Js::DeferEventHandlersPhase) || (m_grfscr & fscrGlobalCode) ) ) { if (m_length > chars) { m_length -= chars; } else { m_length = 0; } if (m_length < Parser::GetDeferralThreshold(this->m_sourceContextInfo->IsSourceProfileLoaded())) { // Stop deferring. m_grfscr &= ~fscrWillDeferFncParse; m_stoppedDeferredParse = TRUE; } } } void Parser::EnsureStackAvailable() { bool isInterrupt = false; if (!m_scriptContext->GetThreadContext()->IsStackAvailable(Js::Constants::MinStackCompile, &isInterrupt)) { Error(isInterrupt ? E_ABORT : VBSERR_OutOfStack); } } void Parser::ThrowNewTargetSyntaxErrForGlobalScope() { // If we are parsing a previously deferred function, we can skip throwing the SyntaxError for `new.target` at global scope. // If we are at global scope, we would have thrown a SyntaxError when we did the Upfront parse pass and we would not have // deferred the function in order to come back now and reparse it. if (m_parseType == ParseType_Deferred) { return; } if (GetCurrentNonLambdaFunctionNode() != nullptr) { return; } if ((this->m_grfscr & fscrEval) != 0) { Js::JavascriptFunction * caller = nullptr; if (Js::JavascriptStackWalker::GetCaller(&caller, m_scriptContext)) { Js::FunctionBody * callerBody = caller->GetFunctionBody(); Assert(callerBody); if (!callerBody->GetIsGlobalFunc() && !(callerBody->IsLambda() && callerBody->GetEnclosedByGlobalFunc())) { return; } } } Error(ERRInvalidNewTarget); } template IdentPtr Parser::ParseMetaProperty(tokens metaParentKeyword, charcount_t ichMin, _Out_opt_ BOOL* pfCanAssign) { AssertMsg(metaParentKeyword == tkNEW, "Only supported for tkNEW parent keywords"); AssertMsg(this->m_token.tk == tkDot, "We must be currently sitting on the dot after the parent keyword"); this->GetScanner()->Scan(); if (this->m_token.tk == tkID && this->m_token.GetIdentifier(this->GetHashTbl()) == this->GetTargetPid()) { ThrowNewTargetSyntaxErrForGlobalScope(); if (pfCanAssign) { *pfCanAssign = FALSE; } return wellKnownPropertyPids._newTarget; } else { Error(ERRsyntax); } } template void Parser::ParseNamedImportOrExportClause(ModuleImportOrExportEntryList* importOrExportEntryList, bool isExportClause) { Assert(m_token.tk == tkLCurly); Assert(importOrExportEntryList != nullptr); this->GetScanner()->Scan(); while (m_token.tk != tkRCurly && m_token.tk != tkEOF) { tokens firstToken = m_token.tk; if (!(m_token.IsIdentifier() || m_token.IsReservedWord())) { Error(ERRsyntax); } IdentPtr identifierName = m_token.GetIdentifier(this->GetHashTbl()); IdentPtr identifierAs = identifierName; this->GetScanner()->Scan(); if (m_token.tk == tkID) { // We have the pattern "IdentifierName as" if (wellKnownPropertyPids.as != m_token.GetIdentifier(this->GetHashTbl())) { Error(ERRsyntax); } this->GetScanner()->Scan(); // If we are parsing an import statement, the token after 'as' must be a BindingIdentifier. if (!isExportClause) { ChkCurTokNoScan(tkID, ERRsyntax); } if (!(m_token.IsIdentifier() || m_token.IsReservedWord())) { Error(ERRsyntax); } identifierAs = m_token.GetIdentifier(this->GetHashTbl()); // Scan to the next token. this->GetScanner()->Scan(); } else if (!isExportClause && firstToken != tkID) { // If we are parsing an import statement and this ImportSpecifier clause did not have // 'as ImportedBinding' at the end of it, identifierName must be a BindingIdentifier. Error(ERRsyntax); } if (m_token.tk == tkComma) { // Consume a trailing comma this->GetScanner()->Scan(); } if (buildAST) { // The name we will use 'as' this import/export is a binding identifier in import statements. if (!isExportClause) { CreateModuleImportDeclNode(identifierAs); AddModuleImportOrExportEntry(importOrExportEntryList, identifierName, identifierAs, nullptr, nullptr); } else { identifierName->SetIsModuleExport(); AddModuleImportOrExportEntry(importOrExportEntryList, nullptr, identifierName, identifierAs, nullptr); } } } // Final token in a named import or export clause must be a '}' ChkCurTokNoScan(tkRCurly, ERRsyntax); } IdentPtrList* Parser::GetRequestedModulesList() { return m_currentNodeProg->AsParseNodeModule()->requestedModules; } ModuleImportOrExportEntryList* Parser::GetModuleImportEntryList() { return m_currentNodeProg->AsParseNodeModule()->importEntries; } ModuleImportOrExportEntryList* Parser::GetModuleLocalExportEntryList() { return m_currentNodeProg->AsParseNodeModule()->localExportEntries; } ModuleImportOrExportEntryList* Parser::GetModuleIndirectExportEntryList() { return m_currentNodeProg->AsParseNodeModule()->indirectExportEntries; } ModuleImportOrExportEntryList* Parser::GetModuleStarExportEntryList() { return m_currentNodeProg->AsParseNodeModule()->starExportEntries; } IdentPtrList* Parser::EnsureRequestedModulesList() { if (m_currentNodeProg->AsParseNodeModule()->requestedModules == nullptr) { m_currentNodeProg->AsParseNodeModule()->requestedModules = Anew(&m_nodeAllocator, IdentPtrList, &m_nodeAllocator); } return m_currentNodeProg->AsParseNodeModule()->requestedModules; } ModuleImportOrExportEntryList* Parser::EnsureModuleImportEntryList() { if (m_currentNodeProg->AsParseNodeModule()->importEntries == nullptr) { m_currentNodeProg->AsParseNodeModule()->importEntries = Anew(&m_nodeAllocator, ModuleImportOrExportEntryList, &m_nodeAllocator); } return m_currentNodeProg->AsParseNodeModule()->importEntries; } ModuleImportOrExportEntryList* Parser::EnsureModuleLocalExportEntryList() { if (m_currentNodeProg->AsParseNodeModule()->localExportEntries == nullptr) { m_currentNodeProg->AsParseNodeModule()->localExportEntries = Anew(&m_nodeAllocator, ModuleImportOrExportEntryList, &m_nodeAllocator); } return m_currentNodeProg->AsParseNodeModule()->localExportEntries; } ModuleImportOrExportEntryList* Parser::EnsureModuleIndirectExportEntryList() { if (m_currentNodeProg->AsParseNodeModule()->indirectExportEntries == nullptr) { m_currentNodeProg->AsParseNodeModule()->indirectExportEntries = Anew(&m_nodeAllocator, ModuleImportOrExportEntryList, &m_nodeAllocator); } return m_currentNodeProg->AsParseNodeModule()->indirectExportEntries; } ModuleImportOrExportEntryList* Parser::EnsureModuleStarExportEntryList() { if (m_currentNodeProg->AsParseNodeModule()->starExportEntries == nullptr) { m_currentNodeProg->AsParseNodeModule()->starExportEntries = Anew(&m_nodeAllocator, ModuleImportOrExportEntryList, &m_nodeAllocator); } return m_currentNodeProg->AsParseNodeModule()->starExportEntries; } void Parser::AddModuleSpecifier(IdentPtr moduleRequest) { IdentPtrList* requestedModulesList = EnsureRequestedModulesList(); if (!requestedModulesList->Has(moduleRequest)) { requestedModulesList->Prepend(moduleRequest); } } ModuleImportOrExportEntry* Parser::AddModuleImportOrExportEntry(ModuleImportOrExportEntryList* importOrExportEntryList, ModuleImportOrExportEntry* importOrExportEntry) { if (importOrExportEntry->exportName != nullptr) { CheckForDuplicateExportEntry(importOrExportEntryList, importOrExportEntry->exportName); } importOrExportEntryList->Prepend(*importOrExportEntry); return importOrExportEntry; } ModuleImportOrExportEntry* Parser::AddModuleImportOrExportEntry(ModuleImportOrExportEntryList* importOrExportEntryList, IdentPtr importName, IdentPtr localName, IdentPtr exportName, IdentPtr moduleRequest) { ModuleImportOrExportEntry* importOrExportEntry = Anew(&m_nodeAllocator, ModuleImportOrExportEntry); importOrExportEntry->importName = importName; importOrExportEntry->localName = localName; importOrExportEntry->exportName = exportName; importOrExportEntry->moduleRequest = moduleRequest; return AddModuleImportOrExportEntry(importOrExportEntryList, importOrExportEntry); } void Parser::AddModuleLocalExportEntry(ParseNodePtr varDeclNode) { AssertOrFailFast(varDeclNode->nop == knopVarDecl || varDeclNode->nop == knopLetDecl || varDeclNode->nop == knopConstDecl); IdentPtr localName = varDeclNode->AsParseNodeVar()->pid; varDeclNode->AsParseNodeVar()->sym->SetIsModuleExportStorage(true); AddModuleImportOrExportEntry(EnsureModuleLocalExportEntryList(), nullptr, localName, localName, nullptr); } void Parser::CheckForDuplicateExportEntry(ModuleImportOrExportEntryList* exportEntryList, IdentPtr exportName) { ModuleImportOrExportEntry* findResult = exportEntryList->Find([&](ModuleImportOrExportEntry exportEntry) { if (exportName == exportEntry.exportName) { return true; } return false; }); if (findResult != nullptr) { Error(ERRsyntax); } } template void Parser::ParseImportClause(ModuleImportOrExportEntryList* importEntryList, bool parsingAfterComma) { bool parsedNamespaceOrNamedImport = false; switch (m_token.tk) { case tkID: // This is the default binding identifier. // If we already saw a comma in the import clause, this is a syntax error. if (parsingAfterComma) { Error(ERRsyntax); } if (buildAST) { IdentPtr localName = m_token.GetIdentifier(this->GetHashTbl()); IdentPtr importName = wellKnownPropertyPids._default; CreateModuleImportDeclNode(localName); AddModuleImportOrExportEntry(importEntryList, importName, localName, nullptr, nullptr); } break; case tkLCurly: // This begins a list of named imports. ParseNamedImportOrExportClause(importEntryList, false); parsedNamespaceOrNamedImport = true; break; case tkStar: // This begins a namespace import clause. // "* as ImportedBinding" // Token following * must be the identifier 'as' this->GetScanner()->Scan(); if (m_token.tk != tkID || wellKnownPropertyPids.as != m_token.GetIdentifier(this->GetHashTbl())) { Error(ERRsyntax); } // Token following 'as' must be a binding identifier. this->GetScanner()->Scan(); ChkCurTokNoScan(tkID, ERRsyntax); if (buildAST) { IdentPtr localName = m_token.GetIdentifier(this->GetHashTbl()); IdentPtr importName = wellKnownPropertyPids._star; CreateModuleImportDeclNode(localName); AddModuleImportOrExportEntry(importEntryList, importName, localName, nullptr, nullptr); } parsedNamespaceOrNamedImport = true; break; default: Error(ERRsyntax); } this->GetScanner()->Scan(); if (m_token.tk == tkComma) { // There cannot be more than one comma in a module import clause. // There cannot be a namespace import or named imports list on the left of the comma in a module import clause. if (parsingAfterComma || parsedNamespaceOrNamedImport) { Error(ERRsyntax); } this->GetScanner()->Scan(); ParseImportClause(importEntryList, true); } } bool Parser::IsImportOrExportStatementValidHere() { ParseNodeFnc * curFunc = GetCurrentFunctionNode(); // Import must be located in the top scope of the module body. return curFunc->nop == knopFncDecl && curFunc->IsModule() && this->m_currentBlockInfo->pnodeBlock == curFunc->pnodeBodyScope && (this->m_grfscr & fscrEvalCode) != fscrEvalCode && this->m_tryCatchOrFinallyDepth == 0 && !this->m_disallowImportExportStmt; } bool Parser::IsTopLevelModuleFunc() { ParseNodeFnc * curFunc = GetCurrentFunctionNode(); return curFunc->nop == knopFncDecl && curFunc->IsModule(); } template ParseNodePtr Parser::ParseImportCall() { this->GetScanner()->Scan(); ParseNodePtr specifier = ParseExpr(koplCma, nullptr, /* fAllowIn */FALSE, /* fAllowEllipsis */FALSE); if (m_token.tk != tkRParen) { Error(ERRnoRparen); } this->GetScanner()->Scan(); return buildAST ? CreateCallNode(knopCall, CreateNodeForOpT(), specifier) : nullptr; } template ParseNodePtr Parser::ParseImport() { Assert(m_scriptContext->GetConfig()->IsES6ModuleEnabled()); Assert(m_token.tk == tkIMPORT); RestorePoint parsedImport; this->GetScanner()->Capture(&parsedImport); this->GetScanner()->Scan(); // import() if (m_token.tk == tkLParen) { if (!m_scriptContext->GetConfig()->IsESDynamicImportEnabled()) { Error(ERRsyntax); } ParseNodePtr pnode = ParseImportCall(); BOOL fCanAssign; IdentToken token; return ParsePostfixOperators(pnode, TRUE, FALSE, FALSE, TRUE, &fCanAssign, &token); } this->GetScanner()->SeekTo(parsedImport); if (!IsImportOrExportStatementValidHere()) { Error(ERRInvalidModuleImportOrExport); } // We just parsed an import token. Next valid token is *, {, string constant, or binding identifier. this->GetScanner()->Scan(); if (m_token.tk == tkStrCon) { // This import declaration has no import clause. // "import ModuleSpecifier;" if (buildAST) { AddModuleSpecifier(m_token.GetStr()); } // Scan past the module identifier. this->GetScanner()->Scan(); } else { ModuleImportOrExportEntryList importEntryList(&m_nodeAllocator); // Parse the import clause (default binding can only exist before the comma). ParseImportClause(&importEntryList); // Token following import clause must be the identifier 'from' IdentPtr moduleSpecifier = ParseImportOrExportFromClause(true); if (buildAST) { Assert(moduleSpecifier != nullptr); AddModuleSpecifier(moduleSpecifier); importEntryList.Map([this, moduleSpecifier](ModuleImportOrExportEntry& importEntry) { importEntry.moduleRequest = moduleSpecifier; AddModuleImportOrExportEntry(EnsureModuleImportEntryList(), &importEntry); }); } importEntryList.Clear(); } // Import statement is actually a nop, we hoist all the imported bindings to the top of the module. return nullptr; } template IdentPtr Parser::ParseImportOrExportFromClause(bool throwIfNotFound) { IdentPtr moduleSpecifier = nullptr; if (m_token.tk == tkID && wellKnownPropertyPids.from == m_token.GetIdentifier(this->GetHashTbl())) { this->GetScanner()->Scan(); // Token following the 'from' token must be a string constant - the module specifier. ChkCurTokNoScan(tkStrCon, ERRsyntax); if (buildAST) { moduleSpecifier = m_token.GetStr(); } this->GetScanner()->Scan(); } else if (throwIfNotFound) { Error(ERRsyntax); } return moduleSpecifier; } template ParseNodePtr Parser::ParseDefaultExportClause() { Assert(m_token.tk == tkDEFAULT); this->GetScanner()->Scan(); ParseNodePtr pnode = nullptr; ushort flags = fFncNoFlgs; switch (m_token.tk) { case tkCLASS: { if (!m_scriptContext->GetConfig()->IsES6ClassAndExtendsEnabled()) { goto LDefault; } // Before we parse the class itself we need to know if the class has an identifier name. // If it does, we'll treat this class as an ordinary class declaration which will bind // it to that name. Otherwise the class should parse as a nameless class expression and // bind only to the export binding. BOOL classHasName = false; RestorePoint parsedClass; this->GetScanner()->Capture(&parsedClass); this->GetScanner()->Scan(); if (m_token.tk == tkID) { classHasName = true; } this->GetScanner()->SeekTo(parsedClass); ParseNodeClass * pnodeClass; pnode = pnodeClass = ParseClassDecl(classHasName, nullptr, nullptr, nullptr); if (buildAST) { AnalysisAssert(pnode != nullptr); Assert(pnode->nop == knopClassDecl); pnodeClass->SetIsDefaultModuleExport(true); } break; } case tkID: // If we parsed an async token, it could either modify the next token (if it is a // function token) or it could be an identifier (let async = 0; export default async;). // To handle both cases, when we parse an async token we need to keep the parser state // and rewind if the next token is not function. if (wellKnownPropertyPids.async == m_token.GetIdentifier(this->GetHashTbl())) { RestorePoint parsedAsync; this->GetScanner()->Capture(&parsedAsync); this->GetScanner()->Scan(); if (m_token.tk == tkFUNCTION) { // Token after async is function, consume the async token and continue to parse the // function as an async function. flags |= fFncAsync; goto LFunction; } // Token after async is not function, no idea what the async token is supposed to mean // so rewind and let the default case handle it. this->GetScanner()->SeekTo(parsedAsync); } goto LDefault; break; case tkFUNCTION: { LFunction: // We just parsed a function token but we need to figure out if the function // has an identifier name or not before we call the helper. RestorePoint parsedFunction; this->GetScanner()->Capture(&parsedFunction); this->GetScanner()->Scan(); if (m_token.tk == tkStar) { // If we saw 'function*' that indicates we are going to parse a generator, // but doesn't tell us if the generator has an identifier or not. // Skip the '*' token for now as it doesn't matter yet. this->GetScanner()->Scan(); } // We say that if the function has an identifier name, it is a 'normal' declaration // and should create a binding to that identifier as well as one for our default export. if (m_token.tk == tkID) { flags |= fFncDeclaration; } else { flags |= fFncNoName; } // Rewind back to the function token and let the helper handle the parsing. this->GetScanner()->SeekTo(parsedFunction); pnode = ParseFncDeclCheckScope(flags); if (buildAST) { AnalysisAssert(pnode != nullptr); Assert(pnode->nop == knopFncDecl); pnode->AsParseNodeFnc()->SetIsDefaultModuleExport(true); } break; } default: LDefault: { ParseNodePtr pnodeExpression = ParseExpr(); // Consider: Can we detect this syntax error earlier? if (pnodeExpression && pnodeExpression->nop == knopComma) { Error(ERRsyntax); } if (buildAST) { AnalysisAssert(pnodeExpression != nullptr); // Mark this node as the default module export. We need to make sure it is put into the correct // module export slot when we emit the node. ParseNodeExportDefault * pnodeExportDefault; pnode = pnodeExportDefault = CreateNodeForOpT(); pnode->AsParseNodeExportDefault()->pnodeExpr = pnodeExpression; } break; } } IdentPtr exportName = wellKnownPropertyPids._default; IdentPtr localName = wellKnownPropertyPids._starDefaultStar; AddModuleImportOrExportEntry(EnsureModuleLocalExportEntryList(), nullptr, localName, exportName, nullptr); return pnode; } template ParseNodePtr Parser::ParseExportDeclaration(bool *needTerminator) { Assert(m_scriptContext->GetConfig()->IsES6ModuleEnabled()); Assert(m_token.tk == tkEXPORT); if (!IsImportOrExportStatementValidHere()) { Error(ERRInvalidModuleImportOrExport); } ParseNodePtr pnode = nullptr; IdentPtr moduleIdentifier = nullptr; tokens declarationType; if (needTerminator != nullptr) { *needTerminator = false; } // We just parsed an export token. Next valid tokens are *, {, var, let, const, async, function, class, default. this->GetScanner()->Scan(); switch (m_token.tk) { case tkStar: this->GetScanner()->Scan(); // A star token in an export declaration must be followed by a from clause which begins with a token 'from'. moduleIdentifier = ParseImportOrExportFromClause(true); if (buildAST) { Assert(moduleIdentifier != nullptr); AddModuleSpecifier(moduleIdentifier); IdentPtr importName = wellKnownPropertyPids._star; AddModuleImportOrExportEntry(EnsureModuleStarExportEntryList(), importName, nullptr, nullptr, moduleIdentifier); } if (needTerminator != nullptr) { *needTerminator = true; } break; case tkLCurly: { ModuleImportOrExportEntryList exportEntryList(&m_nodeAllocator); ParseNamedImportOrExportClause(&exportEntryList, true); this->GetScanner()->Scan(); // Export clause may be followed by a from clause. moduleIdentifier = ParseImportOrExportFromClause(false); if (buildAST) { if (moduleIdentifier != nullptr) { AddModuleSpecifier(moduleIdentifier); } exportEntryList.Map([this, moduleIdentifier](ModuleImportOrExportEntry& exportEntry) { if (moduleIdentifier != nullptr) { exportEntry.moduleRequest = moduleIdentifier; // We need to swap localname and importname when this is a re-export. exportEntry.importName = exportEntry.localName; exportEntry.localName = nullptr; AddModuleImportOrExportEntry(EnsureModuleIndirectExportEntryList(), &exportEntry); } else { AddModuleImportOrExportEntry(EnsureModuleLocalExportEntryList(), &exportEntry); } }); exportEntryList.Clear(); } } if (needTerminator != nullptr) { *needTerminator = true; } break; case tkID: { IdentPtr pid = m_token.GetIdentifier(this->GetHashTbl()); if (wellKnownPropertyPids.let == pid) { declarationType = tkLET; goto ParseVarDecl; } if (wellKnownPropertyPids.async == pid && m_scriptContext->GetConfig()->IsES7AsyncAndAwaitEnabled()) { // In module export statements, async token is only valid if it's followed by function. // We need to check here because ParseStatement would think 'async = 20' is a var decl. RestorePoint parsedAsync; this->GetScanner()->Capture(&parsedAsync); this->GetScanner()->Scan(); if (m_token.tk == tkFUNCTION) { // Token after async is function, rewind to the async token and let ParseStatement handle it. this->GetScanner()->SeekTo(parsedAsync); goto ParseFunctionDecl; } // Token after async is not function, it's a syntax error. } goto ErrorToken; } case tkVAR: case tkLET: case tkCONST: { declarationType = m_token.tk; ParseVarDecl: this->GetScanner()->Scan(); pnode = ParseVariableDeclaration(declarationType, this->GetScanner()->IchMinTok()); if (buildAST) { ForEachItemInList(pnode, [&](ParseNodePtr item) { if (item->nop == knopAsg) { Parser::MapBindIdentifier(item, [&](ParseNodePtr subItem) { AddModuleLocalExportEntry(subItem); }); } else { AddModuleLocalExportEntry(item); } }); } } break; case tkFUNCTION: case tkCLASS: { ParseFunctionDecl: pnode = ParseStatement(); if (buildAST) { IdentPtr localName; if (pnode->nop == knopClassDecl) { ParseNodeClass * pnodeClass = pnode->AsParseNodeClass(); pnodeClass->pnodeDeclName->sym->SetIsModuleExportStorage(true); localName = pnodeClass->pnodeName->pid; } else { Assert(pnode->nop == knopFncDecl); ParseNodeFnc * pnodeFnc = pnode->AsParseNodeFnc(); pnodeFnc->GetFuncSymbol()->SetIsModuleExportStorage(true); localName = pnodeFnc->pid; } Assert(localName != nullptr); AddModuleImportOrExportEntry(EnsureModuleLocalExportEntryList(), nullptr, localName, localName, nullptr); } } break; case tkDEFAULT: { pnode = ParseDefaultExportClause(); } break; default: { ErrorToken: Error(ERRsyntax); } } return pnode; } /*************************************************************************** Parse an expression term. ***************************************************************************/ template ParseNodePtr Parser::ParseTerm(BOOL fAllowCall, LPCOLESTR pNameHint, uint32 *pHintLength, uint32 *pShortNameOffset, _Inout_opt_ IdentToken* pToken /*= nullptr*/, bool fUnaryOrParen /*= false*/, BOOL fCanAssignToCall /*= TRUE*/, _Out_opt_ BOOL* pfCanAssign /*= nullptr*/, _Inout_opt_ BOOL* pfLikelyPattern /*= nullptr*/, _Out_opt_ bool* pfIsDotOrIndex /*= nullptr*/, _Inout_opt_ charcount_t *plastRParen /*= nullptr*/) { ParseNodePtr pnode = nullptr; PidRefStack *savedTopAsyncRef = nullptr; charcount_t ichMin = 0; charcount_t ichLim = 0; size_t iecpMin = 0; size_t iecpLim = 0; size_t iuMin; IdentToken term; BOOL fInNew = FALSE; BOOL fCanAssign = TRUE; bool isAsyncExpr = false; bool isLambdaExpr = false; bool isSpecialName = false; IdentPtr pid = nullptr; Assert(pToken == nullptr || pToken->tk == tkNone); // Must be empty initially PROBE_STACK_NO_DISPOSE(m_scriptContext, Js::Constants::MinStackParseOneTerm); switch (m_token.tk) { case tkID: { pid = m_token.GetIdentifier(this->GetHashTbl()); ichMin = this->GetScanner()->IchMinTok(); iecpMin = this->GetScanner()->IecpMinTok(); ichLim = this->GetScanner()->IchLimTok(); iecpLim = this->GetScanner()->IecpLimTok(); if (pid == wellKnownPropertyPids.async && m_scriptContext->GetConfig()->IsES7AsyncAndAwaitEnabled()) { isAsyncExpr = true; } // Put this into a block to avoid previousAwaitIsKeyword being not initialized after jump to LIdentifier { bool previousAwaitIsKeyword = this->GetScanner()->SetAwaitIsKeywordRegion(isAsyncExpr); this->GetScanner()->Scan(); this->GetScanner()->SetAwaitIsKeywordRegion(previousAwaitIsKeyword); } // We search for an Async expression (a function declaration or an async lambda expression) if (isAsyncExpr && !this->GetScanner()->FHadNewLine()) { if (m_token.tk == tkFUNCTION) { goto LFunction; } else if (m_token.tk == tkID || m_token.tk == tkAWAIT) { isLambdaExpr = true; goto LFunction; } else if (m_token.tk == tkLParen) { // This is potentially an async arrow function. Save the state of the async references // in case it needs to be restored. (Note that the case of a single parameter with no ()'s // is detected upstream and need not be handled here.) savedTopAsyncRef = pid->GetTopRef(); } } CheckArgumentsUse(pid, GetCurrentFunctionNode()); // Assume this pid is not special - overwrite when we parse a special name isSpecialName = false; LIdentifier: PidRefStack * ref = nullptr; // Don't push a reference if this is a single lambda parameter, because we'll reparse with // a correct function ID. if (m_token.tk != tkDArrow) { ref = this->PushPidRef(pid); } if (buildAST) { if (isSpecialName) { pnode = CreateSpecialNameNode(pid, ref, ichMin, ichLim); } else { pnode = CreateNameNode(pid, ref, ichMin, ichLim); } } else { // Remember the identifier start and end in case it turns out to be a statement label. term.tk = tkID; term.pid = pid; // Record the identifier for detection of eval term.ichMin = static_cast(iecpMin); term.ichLim = static_cast(iecpLim); } break; } case tkSUPER: ichMin = this->GetScanner()->IchMinTok(); iecpMin = this->GetScanner()->IecpMinTok(); ichLim = this->GetScanner()->IchLimTok(); iecpLim = this->GetScanner()->IecpLimTok(); if (!m_scriptContext->GetConfig()->IsES6ClassAndExtendsEnabled()) { goto LUnknown; } this->GetScanner()->Scan(); pid = ParseSuper(!!fAllowCall); isSpecialName = true; // Super reference and super call need to push a pid ref to 'this' even when not building an AST ReferenceSpecialName(wellKnownPropertyPids._this, ichMin, ichLim); // Super call needs to reference 'new.target' if (pid == wellKnownPropertyPids._superConstructor) { ReferenceSpecialName(wellKnownPropertyPids._newTarget, ichMin, ichLim); } goto LIdentifier; case tkTHIS: ichMin = this->GetScanner()->IchMinTok(); iecpMin = this->GetScanner()->IecpMinTok(); ichLim = this->GetScanner()->IchLimTok(); iecpLim = this->GetScanner()->IecpLimTok(); pid = wellKnownPropertyPids._this; this->GetScanner()->Scan(); isSpecialName = true; goto LIdentifier; case tkLParen: { ichMin = this->GetScanner()->IchMinTok(); iuMin = this->GetScanner()->IecpMinTok(); // If we are undeferring a function which has deferred stubs, we can check to see if the next deferred stub // is a lambda at the current character. If it is, we know this LParen is the beginning of a lambda nested // function and we can avoid parsing the next series of tokens as a parenthetical expression and reparsing // after finding the => token. if (buildAST && m_currDeferredStub != nullptr && GetCurrentFunctionNode() != nullptr && GetCurrentFunctionNode()->nestedCount < m_currDeferredStubCount) { DeferredFunctionStub* stub = m_currDeferredStub + GetCurrentFunctionNode()->nestedCount; if (stub->ichMin == ichMin) { Assert((stub->fncFlags & kFunctionIsLambda) == kFunctionIsLambda); pnode = ParseFncDeclCheckScope(fFncLambda); break; } } this->GetScanner()->Scan(); if (m_token.tk == tkRParen) { // Empty parens can only be legal as an empty parameter list to a lambda declaration. // We're in a lambda if the next token is =>. fAllowCall = FALSE; this->GetScanner()->Scan(); // If the token after the right paren is not => or if there was a newline between () and => this is a syntax error if (!IsDoingFastScan() && (m_token.tk != tkDArrow || this->GetScanner()->FHadNewLine())) { Error(ERRsyntax); } if (buildAST) { pnode = CreateNodeForOpT(); } break; } // Advance the block ID here in case this parenthetical expression turns out to be a lambda parameter list. // That way the pid ref stacks will be created in their correct final form, and we can simply fix // up function ID's. uint saveNextBlockId = m_nextBlockId; uint saveCurrBlockId = GetCurrentBlock()->blockId; GetCurrentBlock()->blockId = m_nextBlockId++; AutoDeferErrorsRestore deferErrorRestore(this); this->m_funcParenExprDepth++; pnode = ParseExpr(koplNo, &fCanAssign, TRUE, FALSE, nullptr, nullptr /*nameLength*/, nullptr /*pShortNameOffset*/, &term, true, nullptr, plastRParen); this->m_funcParenExprDepth--; if (buildAST && plastRParen) { *plastRParen = this->GetScanner()->IchLimTok(); } ChkCurTok(tkRParen, ERRnoRparen); GetCurrentBlock()->blockId = saveCurrBlockId; if (m_token.tk == tkDArrow) { // We're going to rewind and reinterpret the expression as a parameter list. // Put back the original next-block-ID so the existing pid ref stacks will be correct. m_nextBlockId = saveNextBlockId; } else { // Emit a deferred ... error if one was parsed. if (m_deferEllipsisError) { this->GetScanner()->SeekTo(m_deferEllipsisErrorLoc); Error(ERRInvalidSpreadUse); } else if (m_deferCommaError) { // Emit a comma error if that was deferred. this->GetScanner()->SeekTo(m_deferCommaErrorLoc); Error(ERRsyntax); } } break; } case tkIntCon: if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) { Error(ERRES5NoOctal); } if (buildAST) { pnode = CreateIntNode(m_token.GetLong()); } fCanAssign = FALSE; this->GetScanner()->Scan(); break; case tkFltCon: if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) { Error(ERRES5NoOctal); } if (buildAST) { ParseNodeFloat * pnodeFloat; pnode = pnodeFloat = CreateNodeForOpT(); pnodeFloat->dbl = m_token.GetDouble(); pnodeFloat->maybeInt = m_token.GetDoubleMayBeInt(); } fCanAssign = FALSE; this->GetScanner()->Scan(); break; case tkStrCon: if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) { Error(ERRES5NoOctal); } if (buildAST) { pnode = CreateStrNode(m_token.GetStr()); } else { // Subtract the string literal length from the total char count for the purpose // of deciding whether to defer parsing and byte code generation. this->ReduceDeferredScriptLength(this->GetScanner()->IchLimTok() - this->GetScanner()->IchMinTok()); } fCanAssign = FALSE; this->GetScanner()->Scan(); break; case tkTRUE: if (buildAST) { pnode = CreateNodeForOpT(); } fCanAssign = FALSE; this->GetScanner()->Scan(); break; case tkFALSE: if (buildAST) { pnode = CreateNodeForOpT(); } fCanAssign = FALSE; this->GetScanner()->Scan(); break; case tkNULL: if (buildAST) { pnode = CreateNodeForOpT(); } fCanAssign = FALSE; this->GetScanner()->Scan(); break; case tkDiv: case tkAsgDiv: pnode = ParseRegExp(); fCanAssign = FALSE; this->GetScanner()->Scan(); break; case tkNEW: { ichMin = this->GetScanner()->IchMinTok(); iecpMin = this->GetScanner()->IecpMinTok(); this->GetScanner()->Scan(); if (m_token.tk == tkDot && m_scriptContext->GetConfig()->IsES6ClassAndExtendsEnabled()) { pid = ParseMetaProperty(tkNEW, ichMin, &fCanAssign); ichLim = this->GetScanner()->IchLimTok(); iecpLim = this->GetScanner()->IecpLimTok(); this->GetScanner()->Scan(); isSpecialName = true; goto LIdentifier; } else { ParseNodePtr pnodeExpr = ParseTerm(FALSE, pNameHint, pHintLength, pShortNameOffset, nullptr, false, TRUE, nullptr, nullptr, nullptr, plastRParen); if (buildAST) { pnode = CreateCallNode(knopNew, pnodeExpr, nullptr); pnode->ichMin = ichMin; } fInNew = TRUE; fCanAssign = FALSE; } break; } case tkLBrack: { ichMin = this->GetScanner()->IchMinTok(); this->GetScanner()->Scan(); pnode = ParseArrayLiteral(); if (buildAST) { pnode->ichMin = ichMin; pnode->ichLim = this->GetScanner()->IchLimTok(); } if (this->m_arrayDepth == 0) { Assert(this->GetScanner()->IchLimTok() - ichMin > m_funcInArray); this->ReduceDeferredScriptLength(this->GetScanner()->IchLimTok() - ichMin - this->m_funcInArray); this->m_funcInArray = 0; this->m_funcInArrayDepth = 0; } ChkCurTok(tkRBrack, ERRnoRbrack); if (!IsES6DestructuringEnabled()) { fCanAssign = FALSE; } else if (pfLikelyPattern != nullptr && !IsPostFixOperators()) { *pfLikelyPattern = TRUE; } break; } case tkLCurly: { ichMin = this->GetScanner()->IchMinTok(); this->GetScanner()->ScanForcingPid(); ParseNodePtr pnodeMemberList = ParseMemberList(pNameHint, pHintLength); if (buildAST) { pnode = CreateUniNode(knopObject, pnodeMemberList); pnode->ichMin = ichMin; pnode->ichLim = this->GetScanner()->IchLimTok(); } ChkCurTok(tkRCurly, ERRnoRcurly); if (!IsES6DestructuringEnabled()) { fCanAssign = FALSE; } else if (pfLikelyPattern != nullptr && !IsPostFixOperators()) { *pfLikelyPattern = TRUE; } break; } case tkFUNCTION: { LFunction: if (m_grfscr & fscrDeferredFncExpression) { // The top-level deferred function body was defined by a function expression whose parsing was deferred. We are now // parsing it, so unset the flag so that any nested functions are parsed normally. This flag is only applicable the // first time we see it. // // Normally, deferred functions will be parsed in ParseStatement upon encountering the 'function' token. The first // token of the source code of the function may not a 'function' token though, so we still need to reset this flag // for the first function we parse. This can happen in compat modes, for instance, for a function expression enclosed // in parentheses, where the legacy behavior was to include the parentheses in the function's source code. m_grfscr &= ~fscrDeferredFncExpression; } ushort flags = fFncNoFlgs; if (isLambdaExpr) { flags |= fFncLambda; } if (isAsyncExpr) { flags |= fFncAsync; } pnode = ParseFncDeclNoCheckScope(flags, SuperRestrictionState::Disallowed, pNameHint, /* needsPIDOnRCurlyScan */ false, fUnaryOrParen); if (isAsyncExpr) { pnode->AsParseNodeFnc()->cbStringMin = iecpMin; } fCanAssign = FALSE; break; } case tkCLASS: if (m_scriptContext->GetConfig()->IsES6ClassAndExtendsEnabled()) { pnode = ParseClassDecl(FALSE, pNameHint, pHintLength, pShortNameOffset); } else { goto LUnknown; } fCanAssign = FALSE; break; case tkStrTmplBasic: case tkStrTmplBegin: pnode = ParseStringTemplateDecl(nullptr); fCanAssign = FALSE; break; case tkIMPORT: if (m_scriptContext->GetConfig()->IsES6ModuleEnabled() && m_scriptContext->GetConfig()->IsESDynamicImportEnabled()) { this->GetScanner()->Scan(); ChkCurTokNoScan(tkLParen, ERRnoLparen); pnode = ParseImportCall(); } else { goto LUnknown; } break; #if ENABLE_BACKGROUND_PARSING case tkCASE: { if (!m_doingFastScan) { goto LUnknown; } ParseNodePtr pnodeUnused; pnode = ParseCase(&pnodeUnused); break; } case tkELSE: if (!m_doingFastScan) { goto LUnknown; } this->GetScanner()->Scan(); ParseStatement(); break; #endif default: LUnknown: Error(ERRsyntax); break; } pnode = ParsePostfixOperators(pnode, fAllowCall, fInNew, isAsyncExpr, fCanAssignToCall, &fCanAssign, &term, pfIsDotOrIndex); if (savedTopAsyncRef != nullptr && this->m_token.tk == tkDArrow) { // This is an async arrow function; we're going to back up and reparse it. // Make sure we don't leave behind a bogus reference to the 'async' identifier. for (pid = wellKnownPropertyPids.async; pid->GetTopRef() != savedTopAsyncRef;) { Assert(pid->GetTopRef() != nullptr); pid->RemovePrevPidRef(nullptr); } } // Pass back identifier if requested if (pToken && term.tk == tkID) { *pToken = term; } if (pfCanAssign) { *pfCanAssign = fCanAssign; } return pnode; } template ParseNodeRegExp * Parser::ParseRegExp() { ParseNodeRegExp * pnode = nullptr; if (buildAST || IsDoingFastScan()) { this->GetScanner()->RescanRegExp(); #if ENABLE_BACKGROUND_PARSING BOOL saveDeferringAST = this->m_deferringAST; if (m_doingFastScan) { this->m_deferringAST = false; } #endif pnode = CreateNodeForOpT(); pnode->regexPattern = m_token.GetRegex(); #if ENABLE_BACKGROUND_PARSING if (m_doingFastScan) { this->m_deferringAST = saveDeferringAST; this->AddFastScannedRegExpNode(pnode); if (!buildAST) { pnode = nullptr; } } else if (this->IsBackgroundParser()) { Assert(pnode->regexPattern == nullptr); this->AddBackgroundRegExpNode(pnode); } #endif } else { this->GetScanner()->RescanRegExpNoAST(); } Assert(m_token.tk == tkRegExp); return pnode; } BOOL Parser::NodeIsEvalName(ParseNodePtr pnode) { //WOOB 1107758 Special case of indirect eval binds to local scope in standards mode return pnode->nop == knopName && (pnode->AsParseNodeName()->pid == wellKnownPropertyPids.eval); } BOOL Parser::NodeIsSuperName(ParseNodePtr pnode) { return pnode->nop == knopName && (pnode->AsParseNodeName()->pid == wellKnownPropertyPids._superConstructor); } BOOL Parser::NodeEqualsName(ParseNodePtr pnode, LPCOLESTR sz, uint32 cch) { return pnode->nop == knopName && pnode->AsParseNodeName()->pid->Cch() == cch && !wmemcmp(pnode->AsParseNodeName()->pid->Psz(), sz, cch); } BOOL Parser::NodeIsIdent(ParseNodePtr pnode, IdentPtr pid) { for (;;) { switch (pnode->nop) { case knopName: return (pnode->AsParseNodeName()->pid == pid); case knopComma: pnode = pnode->AsParseNodeBin()->pnode2; break; default: return FALSE; } } } template ParseNodePtr Parser::ParsePostfixOperators( ParseNodePtr pnode, BOOL fAllowCall, BOOL fInNew, BOOL isAsyncExpr, BOOL fCanAssignToCallResult, BOOL *pfCanAssign, _Inout_ IdentToken* pToken, _Out_opt_ bool* pfIsDotOrIndex /*= nullptr */) { uint16 count = 0; bool callOfConstants = false; if (pfIsDotOrIndex) { *pfIsDotOrIndex = false; } for (;;) { uint16 spreadArgCount = 0; switch (m_token.tk) { case tkLParen: { AutoMarkInParsingArgs autoMarkInParsingArgs(this); if (fInNew) { ParseNodePtr pnodeArgs = ParseArgList(&callOfConstants, &spreadArgCount, &count); if (buildAST) { Assert(pnode->nop == knopNew); Assert(pnode->AsParseNodeCall()->pnodeArgs == nullptr); pnode->AsParseNodeCall()->pnodeArgs = pnodeArgs; pnode->AsParseNodeCall()->callOfConstants = callOfConstants; pnode->AsParseNodeCall()->isApplyCall = false; pnode->AsParseNodeCall()->isEvalCall = false; pnode->AsParseNodeCall()->isSuperCall = false; pnode->AsParseNodeCall()->hasDestructuring = m_hasDestructuringPattern; Assert(!m_hasDestructuringPattern || count > 0); pnode->AsParseNodeCall()->argCount = count; pnode->AsParseNodeCall()->spreadArgCount = spreadArgCount; pnode->ichLim = this->GetScanner()->IchLimTok(); } else { pnode = nullptr; pToken->tk = tkNone; // This is no longer an identifier } fInNew = FALSE; ChkCurTok(tkRParen, ERRnoRparen); } else { if (!fAllowCall) { return pnode; } uint saveNextBlockId = m_nextBlockId; uint saveCurrBlockId = GetCurrentBlock()->blockId; if (isAsyncExpr) { // Advance the block ID here in case this parenthetical expression turns out to be a lambda parameter list. // That way the pid ref stacks will be created in their correct final form, and we can simply fix // up function ID's. GetCurrentBlock()->blockId = m_nextBlockId++; } ParseNodePtr pnodeArgs = ParseArgList(&callOfConstants, &spreadArgCount, &count); // We used to un-defer a deferred function body here if it was called as part of the expression that declared it. // We now detect this case up front in ParseFncDecl, which is cheaper and simpler. if (buildAST) { bool fCallIsEval = false; // Detect super() if (this->NodeIsSuperName(pnode)) { pnode = CreateSuperCallNode(pnode->AsParseNodeSpecialName(), pnodeArgs); Assert(pnode); pnode->AsParseNodeSuperCall()->pnodeThis = ReferenceSpecialName(wellKnownPropertyPids._this, pnode->ichMin, this->GetScanner()->IchLimTok(), true); pnode->AsParseNodeSuperCall()->pnodeNewTarget = ReferenceSpecialName(wellKnownPropertyPids._newTarget, pnode->ichMin, this->GetScanner()->IchLimTok(), true); } else { pnode = CreateCallNode(knopCall, pnode, pnodeArgs); Assert(pnode); } // Detect call to "eval" and record it on the function. // Note: we used to leave it up to the byte code generator to detect eval calls // at global scope, but now it relies on the flag the parser sets, so set it here. if (count > 0 && this->NodeIsEvalName(pnode->AsParseNodeCall()->pnodeTarget)) { this->MarkEvalCaller(); fCallIsEval = true; // Eval may reference any of the special symbols so we need to push refs to them here. ReferenceSpecialName(wellKnownPropertyPids._this); ReferenceSpecialName(wellKnownPropertyPids._newTarget); ReferenceSpecialName(wellKnownPropertyPids._super); ReferenceSpecialName(wellKnownPropertyPids._superConstructor); ReferenceSpecialName(wellKnownPropertyPids.arguments); } pnode->AsParseNodeCall()->callOfConstants = callOfConstants; pnode->AsParseNodeCall()->spreadArgCount = spreadArgCount; pnode->AsParseNodeCall()->isApplyCall = false; pnode->AsParseNodeCall()->isEvalCall = fCallIsEval; pnode->AsParseNodeCall()->hasDestructuring = m_hasDestructuringPattern; Assert(!m_hasDestructuringPattern || count > 0); pnode->AsParseNodeCall()->argCount = count; pnode->ichLim = this->GetScanner()->IchLimTok(); } else { pnode = nullptr; if (pToken->tk == tkID && pToken->pid == wellKnownPropertyPids.eval && count > 0) // Detect eval { this->MarkEvalCaller(); ReferenceSpecialName(wellKnownPropertyPids._this); ReferenceSpecialName(wellKnownPropertyPids._newTarget); ReferenceSpecialName(wellKnownPropertyPids._super); ReferenceSpecialName(wellKnownPropertyPids._superConstructor); ReferenceSpecialName(wellKnownPropertyPids.arguments); } pToken->tk = tkNone; // This is no longer an identifier } ChkCurTok(tkRParen, ERRnoRparen); if (isAsyncExpr) { GetCurrentBlock()->blockId = saveCurrBlockId; if (m_token.tk == tkDArrow) { // We're going to rewind and reinterpret the expression as a parameter list. // Put back the original next-block-ID so the existing pid ref stacks will be correct. m_nextBlockId = saveNextBlockId; } } } if (pfCanAssign) { *pfCanAssign = fCanAssignToCallResult && (m_sourceContextInfo ? !PHASE_ON_RAW(Js::EarlyErrorOnAssignToCallPhase, m_sourceContextInfo->sourceContextId, GetCurrentFunctionNode()->functionId) : !PHASE_ON1(Js::EarlyErrorOnAssignToCallPhase)); } if (pfIsDotOrIndex) { *pfIsDotOrIndex = false; } break; } case tkLBrack: { this->GetScanner()->Scan(); IdentToken tok; ParseNodePtr pnodeExpr = ParseExpr(0, FALSE, TRUE, FALSE, nullptr, nullptr, nullptr, &tok); if (buildAST) { AnalysisAssert(pnodeExpr); if (pnode && pnode->nop == knopName && pnode->AsParseNodeName()->IsSpecialName() && pnode->AsParseNodeSpecialName()->isSuper) { pnode = CreateSuperReferenceNode(knopIndex, pnode->AsParseNodeSpecialName(), pnodeExpr); pnode->AsParseNodeSuperReference()->pnodeThis = ReferenceSpecialName(wellKnownPropertyPids._this, pnode->ichMin, pnode->ichLim, true); } else { pnode = CreateBinNode(knopIndex, pnode, pnodeExpr); } AnalysisAssert(pnode); pnode->ichLim = this->GetScanner()->IchLimTok(); } else { pnode = nullptr; pToken->tk = tkNone; // This is no longer an identifier } ChkCurTok(tkRBrack, ERRnoRbrack); if (pfCanAssign) { *pfCanAssign = TRUE; } if (pfIsDotOrIndex) { *pfIsDotOrIndex = true; } PidRefStack * topPidRef = nullptr; if (buildAST) { if (pnodeExpr && pnodeExpr->nop == knopName) { topPidRef = pnodeExpr->AsParseNodeName()->pid->GetTopRef(); } } else if (tok.tk == tkID) { topPidRef = tok.pid->GetTopRef(); } if (topPidRef) { topPidRef->SetIsUsedInLdElem(true); } if (!buildAST) { break; } bool shouldConvertToDot = false; if (pnode->AsParseNodeBin()->pnode2->nop == knopStr) { // if the string is empty or contains escape character, we will not convert them to dot node shouldConvertToDot = pnode->AsParseNodeBin()->pnode2->AsParseNodeStr()->pid->Cch() > 0 && !this->GetScanner()->IsEscapeOnLastTkStrCon(); } if (shouldConvertToDot) { LPCOLESTR str = pnode->AsParseNodeBin()->pnode2->AsParseNodeStr()->pid->Psz(); // See if we can convert o["p"] into o.p and o["0"] into o[0] since they're equivalent and the latter forms // are faster uint32 uintValue; if (Js::JavascriptOperators::TryConvertToUInt32( str, pnode->AsParseNodeBin()->pnode2->AsParseNodeStr()->pid->Cch(), &uintValue) && !Js::TaggedInt::IsOverflow(uintValue)) // the optimization is not very useful if the number can't be represented as a TaggedInt { // No need to verify that uintValue != JavascriptArray::InvalidIndex since all nonnegative TaggedInts are valid indexes auto intNode = CreateIntNode(uintValue); // implicit conversion from uint32 to int32 pnode->AsParseNodeBin()->pnode2 = intNode; } // Field optimization (see GlobOpt::KillLiveElems) checks for value being a Number, // and since NaN/Infinity is a number it won't kill o.NaN/o.Infinity which would cause a problem // if we decide to hoist o.NaN/o.Infinity. // We need to keep o["NaN"] and o["+/-Infinity"] as array element access (we don't hoist that but we may hoist field access), // so no matter if it's killed by o[x] inside a loop, we make sure that we never hoist these. // We need to follow same logic for strings that convert to a floating point number. else { bool doConvertToProperty = false; // Convert a["x"] -> a.x. if (!Parser::IsNaNOrInfinityLiteral(str)) { const OLECHAR* terminalChar; double dbl = Js::NumberUtilities::StrToDbl(str, &terminalChar, m_scriptContext); bool convertsToFloat = !Js::NumberUtilities::IsNan(dbl); doConvertToProperty = !convertsToFloat; } if (doConvertToProperty) { ParseNodeName * pnodeNewExpr = CreateNameNode(pnodeExpr->AsParseNodeStr()->pid); pnodeNewExpr->ichMin = pnodeExpr->ichMin; pnodeNewExpr->ichLim = pnodeExpr->ichLim; pnode->AsParseNodeBin()->pnode2 = pnodeNewExpr; pnode->nop = knopDot; pnode->grfpn |= PNodeFlags::fpnIndexOperator; } } } } break; case tkDot: { ParseNodePtr name = nullptr; OpCode opCode = knopDot; this->GetScanner()->Scan(); if (!m_token.IsIdentifier()) { //allow reserved words in ES5 mode if (!(m_token.IsReservedWord())) { IdentifierExpectedError(m_token); } } // Note: see comment above about field optimization WRT NaN/Infinity/-Infinity. // Convert a.Nan, a.Infinity into a["NaN"], a["Infinity"]. // We don't care about -Infinity case here because x.-Infinity is invalid in JavaScript. // Both NaN and Infinity are identifiers. else if (buildAST && Parser::IsNaNOrInfinityLiteral(m_token.GetIdentifier(this->GetHashTbl())->Psz())) { opCode = knopIndex; } if (buildAST) { if (opCode == knopDot) { name = CreateNameNode(m_token.GetIdentifier(this->GetHashTbl())); } else { Assert(opCode == knopIndex); name = CreateStrNode(m_token.GetIdentifier(this->GetHashTbl())); } if (pnode && pnode->nop == knopName && pnode->AsParseNodeName()->IsSpecialName() && pnode->AsParseNodeSpecialName()->isSuper) { pnode = CreateSuperReferenceNode(opCode, pnode->AsParseNodeSpecialName(), name); pnode->AsParseNodeSuperReference()->pnodeThis = ReferenceSpecialName(wellKnownPropertyPids._this, pnode->ichMin, pnode->ichLim, true); } else { pnode = CreateBinNode(opCode, pnode, name); } } else { pnode = nullptr; pToken->tk = tkNone; } if (pfCanAssign) { *pfCanAssign = TRUE; } if (pfIsDotOrIndex) { *pfIsDotOrIndex = true; } this->GetScanner()->Scan(); break; } case tkStrTmplBasic: case tkStrTmplBegin: { ParseNode* templateNode = nullptr; if (pnode != nullptr) { AutoMarkInParsingArgs autoMarkInParsingArgs(this); templateNode = ParseStringTemplateDecl(pnode); } else { templateNode = ParseStringTemplateDecl(pnode); } if (!buildAST) { pToken->tk = tkNone; // This is no longer an identifier } pnode = templateNode; if (pfCanAssign) { *pfCanAssign = FALSE; } if (pfIsDotOrIndex) { *pfIsDotOrIndex = false; } break; } default: return pnode; } } } /*************************************************************************** Look for an existing label with the given name. ***************************************************************************/ bool Parser::LabelExists(IdentPtr pid, LabelId* pLabelIdList) { StmtNest dummy; dummy.pLabelId = pLabelIdList; dummy.pstmtOuter = m_pstmtCur; // Look through each label list for the current stack of statements for (StmtNest* pStmt = &dummy; pStmt != nullptr; pStmt = pStmt->pstmtOuter) { for (LabelId* pLabelId = pStmt->pLabelId; pLabelId != nullptr; pLabelId = pLabelId->next) { if (pLabelId->pid == pid) return true; } } return false; } // Currently only ints and floats are treated as constants in function call // TODO: Check if we need for other constants as well BOOL Parser::IsConstantInFunctionCall(ParseNodePtr pnode) { if (pnode->nop == knopInt && !Js::TaggedInt::IsOverflow(pnode->AsParseNodeInt()->lw)) { return TRUE; } if (pnode->nop == knopFlt) { return TRUE; } return FALSE; } /*************************************************************************** Parse a list of arguments. ***************************************************************************/ template ParseNodePtr Parser::ParseArgList(bool *pCallOfConstants, uint16 *pSpreadArgCount, uint16 * pCount) { ParseNodePtr pnodeArg; ParseNodePtr pnodeList = nullptr; ParseNodePtr *lastNodeRef = nullptr; // Check for an empty list Assert(m_token.tk == tkLParen); if (this->GetScanner()->Scan() == tkRParen) { return nullptr; } *pCallOfConstants = true; *pSpreadArgCount = 0; int count = 0; while (true) { if (count >= Js::Constants::MaxAllowedArgs) { Error(ERRTooManyArgs); } // Allow spread in argument lists. IdentToken token; pnodeArg = ParseExpr(koplCma, nullptr, TRUE, /* fAllowEllipsis */TRUE, NULL, nullptr, nullptr, &token); ++count; this->MarkEscapingRef(pnodeArg, &token); if (buildAST) { this->CheckArguments(pnodeArg); if (*pCallOfConstants && !IsConstantInFunctionCall(pnodeArg)) { *pCallOfConstants = false; } if (pnodeArg->nop == knopEllipsis) { (*pSpreadArgCount)++; } AddToNodeListEscapedUse(&pnodeList, &lastNodeRef, pnodeArg); } if (m_token.tk != tkComma) { break; } this->GetScanner()->Scan(); if (m_token.tk == tkRParen && m_scriptContext->GetConfig()->IsES7TrailingCommaEnabled()) { break; } } if (pSpreadArgCount != nullptr && (*pSpreadArgCount) > 0) { CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, SpreadFeature, m_scriptContext); } *pCount = static_cast(count); if (buildAST) { Assert(lastNodeRef); Assert(*lastNodeRef); pnodeList->ichLim = (*lastNodeRef)->ichLim; } return pnodeList; } // Currently only ints are treated as constants in ArrayLiterals BOOL Parser::IsConstantInArrayLiteral(ParseNodePtr pnode) { if (pnode->nop == knopInt && !Js::TaggedInt::IsOverflow(pnode->AsParseNodeInt()->lw)) { return TRUE; } return FALSE; } template ParseNodeArrLit * Parser::ParseArrayLiteral() { ParseNodeArrLit * pnode = nullptr; bool arrayOfTaggedInts = false; bool arrayOfInts = false; bool arrayOfNumbers = false; bool hasMissingValues = false; uint count = 0; uint spreadCount = 0; ParseNodePtr pnode1 = ParseArrayList(&arrayOfTaggedInts, &arrayOfInts, &arrayOfNumbers, &hasMissingValues, &count, &spreadCount); if (buildAST) { pnode = CreateNodeForOpT(); pnode->pnode1 = pnode1; pnode->arrayOfTaggedInts = arrayOfTaggedInts; pnode->arrayOfInts = arrayOfInts; pnode->arrayOfNumbers = arrayOfNumbers; pnode->hasMissingValues = hasMissingValues; pnode->count = count; pnode->spreadCount = spreadCount; if (pnode->pnode1) { this->CheckArguments(pnode->pnode1); } } return pnode; } /*************************************************************************** Create an ArrayLiteral node Parse a list of array elements. [ a, b, , c, ] ***************************************************************************/ template ParseNodePtr Parser::ParseArrayList(bool *pArrayOfTaggedInts, bool *pArrayOfInts, bool *pArrayOfNumbers, bool *pHasMissingValues, uint *count, uint *spreadCount) { ParseNodePtr pnodeArg = nullptr; ParseNodePtr pnodeList = nullptr; ParseNodePtr *lastNodeRef = nullptr; *count = 0; // Check for an empty list if (tkRBrack == m_token.tk) { return nullptr; } this->m_arrayDepth++; bool arrayOfTaggedInts = buildAST; bool arrayOfInts = buildAST; bool arrayOfNumbers = buildAST; bool arrayOfVarInts = false; bool hasMissingValues = false; for (;;) { (*count)++; if (tkComma == m_token.tk || tkRBrack == m_token.tk) { hasMissingValues = true; arrayOfTaggedInts = false; arrayOfInts = false; arrayOfNumbers = false; if (buildAST) { pnodeArg = CreateNodeForOpT(); } } else { // Allow Spread in array literals. pnodeArg = ParseExpr(koplCma, nullptr, TRUE, /* fAllowEllipsis */ TRUE); if (buildAST) { if (pnodeArg->nop == knopEllipsis) { (*spreadCount)++; } this->CheckArguments(pnodeArg); } } #if DEBUG if (m_grfscr & fscrEnforceJSON && !IsJSONValid(pnodeArg)) { Error(ERRsyntax); } #endif if (buildAST) { if (arrayOfNumbers) { if (pnodeArg->nop != knopInt) { arrayOfTaggedInts = false; if (pnodeArg->nop != knopFlt) { // Not an array of constants. arrayOfInts = false; arrayOfNumbers = false; } else if (arrayOfInts && Js::JavascriptNumber::IsInt32OrUInt32(pnodeArg->AsParseNodeFloat()->dbl) && (!Js::JavascriptNumber::IsInt32(pnodeArg->AsParseNodeFloat()->dbl) || pnodeArg->AsParseNodeFloat()->dbl == -2147483648.0)) { // We've seen nothing but ints, and this is a uint32 but not an int32. // Unless we see an actual float at some point, we want an array of vars // so we can work with tagged ints. arrayOfVarInts = true; } else { // Not an int array, but it may still be a float array. arrayOfInts = false; } } else { if (Js::SparseArraySegment::IsMissingItem((int32*)&pnodeArg->AsParseNodeInt()->lw)) { arrayOfInts = false; } if (Js::TaggedInt::IsOverflow(pnodeArg->AsParseNodeInt()->lw)) { arrayOfTaggedInts = false; } } } AddToNodeListEscapedUse(&pnodeList, &lastNodeRef, pnodeArg); } if (tkComma != m_token.tk) { break; } this->GetScanner()->Scan(); if (tkRBrack == m_token.tk) { break; } } if (spreadCount != nullptr && *spreadCount > 0) { CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, SpreadFeature, m_scriptContext); } if (buildAST) { Assert(lastNodeRef); Assert(*lastNodeRef); pnodeList->ichLim = (*lastNodeRef)->ichLim; if (arrayOfVarInts && arrayOfInts) { arrayOfInts = false; arrayOfNumbers = false; } *pArrayOfTaggedInts = arrayOfTaggedInts; *pArrayOfInts = arrayOfInts; *pArrayOfNumbers = arrayOfNumbers; *pHasMissingValues = hasMissingValues; } this->m_arrayDepth--; return pnodeList; } Parser::MemberNameToTypeMap* Parser::CreateMemberNameMap(ArenaAllocator* pAllocator) { Assert(pAllocator); return Anew(pAllocator, MemberNameToTypeMap, pAllocator, 5); } template void Parser::ParseComputedName(ParseNodePtr* ppnodeName, LPCOLESTR* ppNameHint, LPCOLESTR* ppFullNameHint, uint32 *pNameLength, uint32 *pShortNameOffset) { this->GetScanner()->Scan(); ParseNodePtr pnodeNameExpr = ParseExpr(koplCma, nullptr, TRUE, FALSE, *ppNameHint, pNameLength, pShortNameOffset); if (buildAST) { *ppnodeName = CreateUniNode(knopComputedName, pnodeNameExpr, pnodeNameExpr->ichMin, pnodeNameExpr->ichLim); } if (ppFullNameHint && buildAST && CONFIG_FLAG(UseFullName)) { *ppFullNameHint = FormatPropertyString(*ppNameHint, pnodeNameExpr, pNameLength, pShortNameOffset); } ChkCurTokNoScan(tkRBrack, ERRnoRbrack); } /*************************************************************************** Parse a list of object set/get members, e.g.: { get foo(){ ... }, set bar(arg) { ... } } ***************************************************************************/ template ParseNodeBin * Parser::ParseMemberGetSet(OpCode nop, LPCOLESTR* ppNameHint, size_t iecpMin, charcount_t ichMin) { ParseNodePtr pnodeName = nullptr; Assert(nop == knopGetMember || nop == knopSetMember); Assert(ppNameHint); IdentPtr pid = nullptr; bool isComputedName = false; *ppNameHint = nullptr; switch (m_token.tk) { default: if (!m_token.IsReservedWord()) { Error(ERRnoMemberIdent); } // fall through case tkID: pid = m_token.GetIdentifier(this->GetHashTbl()); *ppNameHint = pid->Psz(); if (buildAST) { pnodeName = CreateStrNode(pid); } break; case tkStrCon: if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) { Error(ERRES5NoOctal); } pid = m_token.GetStr(); *ppNameHint = pid->Psz(); if (buildAST) { pnodeName = CreateStrNode(pid); } break; case tkIntCon: if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) { Error(ERRES5NoOctal); } pid = this->GetScanner()->PidFromLong(m_token.GetLong()); if (buildAST) { pnodeName = CreateStrNode(pid); } break; case tkFltCon: if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) { Error(ERRES5NoOctal); } pid = this->GetScanner()->PidFromDbl(m_token.GetDouble()); if (buildAST) { pnodeName = CreateStrNode(pid); } break; case tkLBrack: // Computed property name: get|set [expr] () { } if (!m_scriptContext->GetConfig()->IsES6ObjectLiteralsEnabled()) { Error(ERRnoMemberIdent); } LPCOLESTR emptyHint = nullptr; uint32 offset = 0; ParseComputedName(&pnodeName, &emptyHint, ppNameHint, &offset); isComputedName = true; break; } MemberType memberType; ushort flags = fFncMethod | fFncNoName; if (nop == knopGetMember) { memberType = MemberTypeGetter; flags |= fFncNoArg; } else { Assert(nop == knopSetMember); memberType = MemberTypeSetter; flags |= fFncOneArg; } ParseNodeFnc * pnodeFnc = ParseFncDeclNoCheckScope(flags, SuperRestrictionState::PropertyAllowed, *ppNameHint, /*needsPIDOnRCurlyScan*/ false); pnodeFnc->cbStringMin = iecpMin; if (isComputedName) { pnodeFnc->SetHasComputedName(); } pnodeFnc->SetHasHomeObj(); pnodeFnc->SetIsAccessor(); if (buildAST) { return CreateBinNode(nop, pnodeName, pnodeFnc); } else { return nullptr; } } /*************************************************************************** Parse a list of object members. e.g. { x:foo, 'y me':bar } ***************************************************************************/ template ParseNodePtr Parser::ParseMemberList(LPCOLESTR pNameHint, uint32* pNameHintLength, tokens declarationType) { ParseNodeBin * pnodeArg = nullptr; ParseNodePtr pnodeEllipsis = nullptr; ParseNodePtr pnodeName = nullptr; ParseNodePtr pnodeList = nullptr; ParseNodePtr *lastNodeRef = nullptr; LPCOLESTR pFullNameHint = nullptr; // A calculated full name uint32 fullNameHintLength = pNameHintLength ? *pNameHintLength : 0; uint32 shortNameOffset = 0; bool isProtoDeclared = false; bool seenRest = false; // we get declaration tkLCurly - when the possible object pattern found under the expression. bool isObjectPattern = (declarationType == tkVAR || declarationType == tkLET || declarationType == tkCONST || declarationType == tkLCurly) && IsES6DestructuringEnabled(); // Check for an empty list if (tkRCurly == m_token.tk) { return nullptr; } ArenaAllocator tempAllocator(_u("MemberNames"), m_nodeAllocator.GetPageAllocator(), Parser::OutOfMemory); bool hasDeferredInitError = false; for (;;) { bool isComputedName = false; #if DEBUG if ((m_grfscr & fscrEnforceJSON) && (tkStrCon != m_token.tk || !(this->GetScanner()->IsDoubleQuoteOnLastTkStrCon()))) { Error(ERRsyntax); } #endif bool isAsyncMethod = false; charcount_t ichMin = this->GetScanner()->IchMinTok(); size_t iecpMin = this->GetScanner()->IecpMinTok(); if (m_token.tk == tkID && m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.async && m_scriptContext->GetConfig()->IsES7AsyncAndAwaitEnabled()) { RestorePoint parsedAsync; this->GetScanner()->Capture(&parsedAsync); ichMin = this->GetScanner()->IchMinTok(); iecpMin = this->GetScanner()->IecpMinTok(); this->GetScanner()->ScanForcingPid(); if (m_token.tk == tkLParen || m_token.tk == tkColon || m_token.tk == tkRCurly || this->GetScanner()->FHadNewLine() || m_token.tk == tkComma) { this->GetScanner()->SeekTo(parsedAsync); } else { isAsyncMethod = true; } } bool isGenerator = m_scriptContext->GetConfig()->IsES6GeneratorsEnabled() && m_token.tk == tkStar; ushort fncDeclFlags = fFncNoName | fFncMethod; if (isGenerator) { if (isAsyncMethod) { Error(ERRsyntax); } // Include star character in the function extents ichMin = this->GetScanner()->IchMinTok(); iecpMin = this->GetScanner()->IecpMinTok(); this->GetScanner()->ScanForcingPid(); fncDeclFlags |= fFncGenerator; } IdentPtr pidHint = nullptr; // A name scoped to current expression Token tkHint = m_token; charcount_t idHintIchMin = static_cast(this->GetScanner()->IecpMinTok()); charcount_t idHintIchLim = static_cast(this->GetScanner()->IecpLimTok()); bool wrapInBrackets = false; bool seenEllipsis = false; switch (m_token.tk) { default: if (!m_token.IsReservedWord()) { Error(ERRnoMemberIdent); } // allow reserved words wrapInBrackets = true; // fall-through case tkID: pidHint = m_token.GetIdentifier(this->GetHashTbl()); if (buildAST) { pnodeName = CreateStrNode(pidHint); } break; case tkStrCon: if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) { Error(ERRES5NoOctal); } wrapInBrackets = true; pidHint = m_token.GetStr(); if (buildAST) { pnodeName = CreateStrNode(pidHint); } break; case tkIntCon: // Object initializers with numeric labels allowed in JS6 if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) { Error(ERRES5NoOctal); } pidHint = this->GetScanner()->PidFromLong(m_token.GetLong()); if (buildAST) { pnodeName = CreateStrNode(pidHint); } break; case tkFltCon: if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) { Error(ERRES5NoOctal); } pidHint = this->GetScanner()->PidFromDbl(m_token.GetDouble()); if (buildAST) { pnodeName = CreateStrNode(pidHint); } wrapInBrackets = true; break; case tkLBrack: // Computed property name: [expr] : value if (!m_scriptContext->GetConfig()->IsES6ObjectLiteralsEnabled()) { Error(ERRnoMemberIdent); } ParseComputedName(&pnodeName, &pNameHint, &pFullNameHint, &fullNameHintLength, &shortNameOffset); isComputedName = true; break; case tkEllipsis: if (CONFIG_FLAG(ES2018ObjectRestSpread)) { seenEllipsis = true; } else { Error(ERRnoMemberIdent); } break; } if (pFullNameHint == nullptr) { if (CONFIG_FLAG(UseFullName)) { pFullNameHint = AppendNameHints(pNameHint, pidHint, &fullNameHintLength, &shortNameOffset, false, wrapInBrackets); } else { pFullNameHint = pidHint ? pidHint->Psz() : nullptr; fullNameHintLength = pidHint ? pidHint->Cch() : 0; shortNameOffset = 0; } } RestorePoint atPid; // Only move to next token if spread op was not seen if (!seenEllipsis) { this->GetScanner()->Capture(&atPid); this->GetScanner()->ScanForcingPid(); } if (isGenerator && m_token.tk != tkLParen) { Error(ERRnoLparen); } if (tkColon == m_token.tk) { // It is a syntax error if the production of the form __proto__ : <> occurs more than once. From B.3.1 in spec. // Note that previous scan is important because only after that we can determine we have a variable. if (!isComputedName && pidHint == wellKnownPropertyPids.__proto__) { if (isProtoDeclared) { Error(ERRsyntax); } else { isProtoDeclared = true; } } this->GetScanner()->Scan(); ParseNodePtr pnodeExpr = nullptr; if (isObjectPattern) { if (m_token.tk == tkEllipsis) { Error(ERRUnexpectedEllipsis); } RestorePoint atExpression; if (!buildAST && declarationType == tkLCurly && IsPossiblePatternStart()) { this->GetScanner()->Capture(&atExpression); int saveNextBlockId = m_nextBlockId; // It is possible that we might encounter the shorthand init error. Lets find that out. bool savedDeferredInitError = m_hasDeferredShorthandInitError; m_hasDeferredShorthandInitError = false; IdentToken token; BOOL fLikelyPattern = false; // First identify that the current expression is indeed the object/array literal. Otherwise we will just use the ParsrExpr to parse that. ParseTerm(/* fAllowCall */ m_token.tk != tkSUPER, nullptr /*pNameHint*/, nullptr /*pHintLength*/, nullptr /*pShortNameOffset*/, &token, false /*fUnaryOrParen*/, TRUE, nullptr /*pfCanAssign*/, &fLikelyPattern); m_nextBlockId = saveNextBlockId; this->GetScanner()->SeekTo(atExpression); if (fLikelyPattern) { pnodeExpr = ParseDestructuredVarDecl(declarationType, declarationType != tkLCurly, &seenRest/* *hasSeenRest*/, false /*topLevel*/, false /*allowEmptyExpression*/, true /*isObjectPattern*/); if (m_token.tk != tkComma && m_token.tk != tkRCurly) { if (m_token.IsOperator()) { Error(ERRDestructNoOper); } Error(ERRsyntax); } } else { if (m_hasDeferredShorthandInitError) { Error(ERRnoColon); } pnodeExpr = ParseExpr(koplCma, nullptr/*pfCantAssign*/, TRUE/*fAllowIn*/, FALSE/*fAllowEllipsis*/, pFullNameHint, &fullNameHintLength, &shortNameOffset); } m_hasDeferredShorthandInitError = savedDeferredInitError; } else { pnodeExpr = ParseDestructuredVarDecl(declarationType, declarationType != tkLCurly, &seenRest/* *hasSeenRest*/, false /*topLevel*/, false /*allowEmptyExpression*/, true /*isObjectPattern*/); if (m_token.tk != tkComma && m_token.tk != tkRCurly) { if (m_token.IsOperator()) { Error(ERRDestructNoOper); } Error(ERRsyntax); } } } else { pnodeExpr = ParseExpr(koplCma, nullptr/*pfCantAssign*/, TRUE/*fAllowIn*/, FALSE/*fAllowEllipsis*/, pFullNameHint, &fullNameHintLength, &shortNameOffset); if (pnodeExpr && pnodeExpr->nop == knopFncDecl) { ParseNodeFnc* funcNode = pnodeExpr->AsParseNodeFnc(); if (isComputedName) { funcNode->SetHasComputedName(); } funcNode->SetHasHomeObj(); } } #if DEBUG if ((m_grfscr & fscrEnforceJSON) && !IsJSONValid(pnodeExpr)) { Error(ERRsyntax); } #endif if (buildAST) { pnodeArg = CreateBinNode(isObjectPattern ? knopObjectPatternMember : knopMember, pnodeName, pnodeExpr); if (pnodeArg->pnode1->nop == knopStr) { pnodeArg->pnode1->AsParseNodeStr()->pid->PromoteAssignmentState(); } } } else if (m_token.tk == tkLParen && m_scriptContext->GetConfig()->IsES6ObjectLiteralsEnabled()) { if (isObjectPattern) { Error(ERRInvalidAssignmentTarget); } // Shorthand syntax: foo() {} -> foo: function() {} // Rewind to the PID and parse a function expression. this->GetScanner()->SeekTo(atPid); ParseNodeFnc * pnodeFnc = ParseFncDeclNoCheckScope(fncDeclFlags | (isAsyncMethod ? fFncAsync : fFncNoFlgs), SuperRestrictionState::PropertyAllowed, pFullNameHint, /*needsPIDOnRCurlyScan*/ false); if (isAsyncMethod || isGenerator) { pnodeFnc->cbStringMin = iecpMin; } if (isComputedName) { pnodeFnc->SetHasComputedName(); pnodeFnc->cbStringMin = iecpMin; } pnodeFnc->SetHasHomeObj(); if (buildAST) { pnodeArg = CreateBinNode(knopMember, pnodeName, pnodeFnc); } } else if (seenEllipsis) { if (!isObjectPattern) { pnodeEllipsis = ParseExpr(koplCma, nullptr, TRUE, /* fAllowEllipsis */ TRUE); } else { pnodeEllipsis = ParseDestructuredVarDecl(declarationType, declarationType != tkLCurly, &seenRest/* *hasSeenRest*/, false /*topLevel*/, false /*allowEmptyExpression*/, true /*isObjectPattern*/); } if (buildAST) { this->CheckArguments(pnodeEllipsis); } } else if (nullptr != pidHint) //It's either tkID/tkStrCon/tkFloatCon/tkIntCon { Assert(pidHint->Psz() != nullptr); if ((pidHint == wellKnownPropertyPids.get || pidHint == wellKnownPropertyPids.set) && // get/set are only pseudo keywords when they are identifiers (i.e. not strings) tkHint.tk == tkID && NextTokenIsPropertyNameStart()) { if (isObjectPattern) { Error(ERRInvalidAssignmentTarget); } LPCOLESTR pNameGetOrSet = nullptr; OpCode op = pidHint == wellKnownPropertyPids.get ? knopGetMember : knopSetMember; pnodeArg = ParseMemberGetSet(op, &pNameGetOrSet, iecpMin, ichMin); if (CONFIG_FLAG(UseFullName) && buildAST && pnodeArg->pnode2->nop == knopFncDecl) { // displays as "get object.funcname" or "set object.funcname" uint32 getOrSetOffset = 0; LPCOLESTR intermediateHint = AppendNameHints(pNameHint, pNameGetOrSet, &fullNameHintLength, &shortNameOffset); pFullNameHint = AppendNameHints(pidHint, intermediateHint, &fullNameHintLength, &getOrSetOffset, true); shortNameOffset += getOrSetOffset; } } else if ((m_token.tk == tkRCurly || m_token.tk == tkComma || m_token.tk == tkAsg) && m_scriptContext->GetConfig()->IsES6ObjectLiteralsEnabled()) { // Shorthand {foo} -> {foo:foo} syntax. // {foo = } supported only when on object pattern rules are being applied if (tkHint.tk != tkID) { Assert(tkHint.IsReservedWord() || tkHint.tk == tkIntCon || tkHint.tk == tkFltCon || tkHint.tk == tkStrCon); // All keywords are banned in non-strict mode. // Future reserved words are banned in strict mode. if (IsStrictMode() || !tkHint.IsFutureReservedWord(true)) { IdentifierExpectedError(tkHint); } } if (buildAST) { CheckArgumentsUse(pidHint, GetCurrentFunctionNode()); } bool couldBeObjectPattern = !isObjectPattern && m_token.tk == tkAsg; // Saving the current state as we may change the isObjectPattern down below. bool oldState = isObjectPattern; if (couldBeObjectPattern) { declarationType = tkLCurly; isObjectPattern = true; // This may be an error but we are deferring for favouring destructuring. hasDeferredInitError = true; } ParseNodePtr pnodeIdent = nullptr; if (isObjectPattern) { this->GetScanner()->SeekTo(atPid); pnodeIdent = ParseDestructuredVarDecl(declarationType, declarationType != tkLCurly, &seenRest/* *hasSeenRest*/, false /*topLevel*/, false /*allowEmptyExpression*/, true /*isObjectPattern*/); if (m_token.tk != tkComma && m_token.tk != tkRCurly) { if (m_token.IsOperator()) { Error(ERRDestructNoOper); } Error(ERRsyntax); } } else { // Add a reference to the hinted name so we can bind it properly. PidRefStack *ref = PushPidRef(pidHint); if (buildAST) { pnodeIdent = CreateNameNode(pidHint, ref, idHintIchMin, idHintIchLim); } } if (buildAST) { pnodeArg = CreateBinNode(isObjectPattern && !couldBeObjectPattern ? knopObjectPatternMember : knopMemberShort, pnodeName, pnodeIdent); } isObjectPattern = oldState; } else { Error(ERRnoColon); } } else { Error(ERRnoColon); } if (buildAST) { if (seenEllipsis) { Assert(pnodeEllipsis != nullptr); AddToNodeListEscapedUse(&pnodeList, &lastNodeRef, pnodeEllipsis); } else { Assert(pnodeArg->pnode2 != nullptr); if (pnodeArg->pnode2->nop == knopFncDecl) { Assert(fullNameHintLength >= shortNameOffset); ParseNodeFnc * pnodeFunc = pnodeArg->pnode2->AsParseNodeFnc(); pnodeFunc->hint = pFullNameHint; pnodeFunc->hintLength = fullNameHintLength; pnodeFunc->hintOffset = shortNameOffset; } AddToNodeListEscapedUse(&pnodeList, &lastNodeRef, pnodeArg); } } pidHint = nullptr; pFullNameHint = nullptr; if (tkComma != m_token.tk) { break; } this->GetScanner()->ScanForcingPid(); if (tkRCurly == m_token.tk) { break; } if (seenRest) // Rest must be in the last position. { Error(ERRDestructRestLast); } } m_hasDeferredShorthandInitError = m_hasDeferredShorthandInitError || hasDeferredInitError; if (buildAST) { Assert(lastNodeRef); Assert(*lastNodeRef); pnodeList->ichLim = (*lastNodeRef)->ichLim; } return pnodeList; } BOOL Parser::WillDeferParse(Js::LocalFunctionId functionId) { if ((m_grfscr & fscrWillDeferFncParse) != 0) { if (m_stoppedDeferredParse) { return false; } if (!PHASE_ENABLED_RAW(DeferParsePhase, m_sourceContextInfo->sourceContextId, functionId)) { return false; } if (PHASE_FORCE_RAW(Js::DeferParsePhase, m_sourceContextInfo->sourceContextId, functionId) #ifdef ENABLE_DEBUG_CONFIG_OPTIONS || Js::Configuration::Global.flags.IsEnabled(Js::ForceUndoDeferFlag) #endif ) { return true; } #if ENABLE_PROFILE_INFO #ifndef DISABLE_DYNAMIC_PROFILE_DEFER_PARSE if (m_sourceContextInfo->sourceDynamicProfileManager != nullptr) { Js::ExecutionFlags flags = m_sourceContextInfo->sourceDynamicProfileManager->IsFunctionExecuted(functionId); return flags != Js::ExecutionFlags_Executed; } #endif #endif return true; } return false; } // // Call this in ParseFncDecl only to check (and reset) if ParseFncDecl is re-parsing a deferred // function body. If a deferred function is called and being re-parsed, it shouldn't be deferred again. // BOOL Parser::IsDeferredFnc() { if (m_grfscr & fscrDeferredFnc) { m_grfscr &= ~fscrDeferredFnc; return true; } return false; } template ParseNode * Parser::ParseFncDeclCheckScope(ushort flags, bool fAllowIn) { ParseNodeBlock * pnodeFncBlockScope = nullptr; ParseNodePtr *ppnodeScopeSave = nullptr; ParseNodePtr *ppnodeExprScopeSave = nullptr; bool fDeclaration = flags & fFncDeclaration; bool noStmtContext = false; if (fDeclaration) { noStmtContext = m_pstmtCur->GetNop() != knopBlock; if (noStmtContext) { // We have a function declaration like "if (a) function f() {}". We didn't see // a block scope on the way in, so we need to pretend we did. Note that this is a syntax error // in strict mode. if (!this->FncDeclAllowedWithoutContext(flags)) { Error(ERRsyntax); } pnodeFncBlockScope = StartParseBlock(PnodeBlockType::Regular, ScopeType_Block); if (buildAST) { PushFuncBlockScope(pnodeFncBlockScope, &ppnodeScopeSave, &ppnodeExprScopeSave); } } } ParseNodeFnc * pnodeFnc = ParseFncDeclInternal(flags, nullptr, /* needsPIDOnRCurlyScan */ false, /* fUnaryOrParen */ false, noStmtContext, SuperRestrictionState::Disallowed, fAllowIn); if (pnodeFncBlockScope) { Assert(pnodeFncBlockScope->pnodeStmt == nullptr); pnodeFncBlockScope->pnodeStmt = pnodeFnc; if (buildAST) { PopFuncBlockScope(ppnodeScopeSave, ppnodeExprScopeSave); } FinishParseBlock(pnodeFncBlockScope); return pnodeFncBlockScope; } return pnodeFnc; } template ParseNodeFnc * Parser::ParseFncDeclNoCheckScope(ushort flags, SuperRestrictionState::State superRestrictionState, LPCOLESTR pNameHint, const bool needsPIDOnRCurlyScan, bool fUnaryOrParen, bool fAllowIn) { Assert((flags & fFncDeclaration) == 0); return ParseFncDeclInternal(flags, pNameHint, needsPIDOnRCurlyScan, fUnaryOrParen, /* noStmtContext */ false, superRestrictionState, fAllowIn); } template ParseNodeFnc * Parser::ParseFncDeclInternal(ushort flags, LPCOLESTR pNameHint, const bool needsPIDOnRCurlyScan, bool fUnaryOrParen, bool noStmtContext, SuperRestrictionState::State superRestrictionState, bool fAllowIn) { ParseNodeFnc * pnodeFnc = nullptr; ParseNodePtr *ppnodeVarSave = nullptr; bool fDeclaration = flags & fFncDeclaration; bool fModule = (flags & fFncModule) != 0; bool fLambda = (flags & fFncLambda) != 0; charcount_t ichMin = this->GetScanner()->IchMinTok(); bool wasInDeferredNestedFunc = false; uint tryCatchOrFinallyDepthSave = this->m_tryCatchOrFinallyDepth; this->m_tryCatchOrFinallyDepth = 0; if (this->m_arrayDepth) { this->m_funcInArrayDepth++; // Count function depth within array literal } // Update the count of functions nested in the current parent. Assert(m_pnestedCount || !buildAST); uint *pnestedCountSave = m_pnestedCount; if (buildAST || m_pnestedCount) { (*m_pnestedCount)++; } uint scopeCountNoAstSave = m_scopeCountNoAst; m_scopeCountNoAst = 0; // Create the node. pnodeFnc = CreateAllowDeferNodeForOpT(); pnodeFnc->SetDeclaration(fDeclaration); pnodeFnc->nestedFuncEscapes = false; pnodeFnc->cbMin = this->GetScanner()->IecpMinTok(); pnodeFnc->cbStringMin = pnodeFnc->cbMin; pnodeFnc->functionId = (*m_nextFunctionId)++; pnodeFnc->superRestrictionState = superRestrictionState; // Push new parser state with this new function node AppendFunctionToScopeList(fDeclaration, pnodeFnc); // Start the argument list. ppnodeVarSave = m_ppnodeVar; if (buildAST) { pnodeFnc->lineNumber = this->GetScanner()->LineCur(); pnodeFnc->columnNumber = CalculateFunctionColumnNumber(); pnodeFnc->SetNested(m_currentNodeFunc != nullptr); // If there is a current function, then we're a nested function. pnodeFnc->SetStrictMode(IsStrictMode()); // Inherit current strict mode -- may be overridden by the function itself if it contains a strict mode directive. m_pCurrentAstSize = &pnodeFnc->astSize; } else // if !buildAST { wasInDeferredNestedFunc = m_inDeferredNestedFunc; m_inDeferredNestedFunc = true; } m_pnestedCount = &pnodeFnc->nestedCount; AnalysisAssert(pnodeFnc); pnodeFnc->SetIsAsync((flags & fFncAsync) != 0); pnodeFnc->SetIsLambda(fLambda); pnodeFnc->SetIsMethod((flags & fFncMethod) != 0); pnodeFnc->SetIsClassMember((flags & fFncClassMember) != 0); pnodeFnc->SetIsModule(fModule); pnodeFnc->SetIsClassConstructor((flags & fFncClassConstructor) != 0); pnodeFnc->SetIsBaseClassConstructor((flags & fFncBaseClassConstructor) != 0); pnodeFnc->SetHomeObjLocation(Js::Constants::NoRegister); IdentPtr pFncNamePid = nullptr; bool needScanRCurly = true; ParseFncDeclHelper(pnodeFnc, pNameHint, flags, fUnaryOrParen, noStmtContext, &needScanRCurly, fModule, &pFncNamePid, fAllowIn); AddNestedCapturedNames(pnodeFnc); AnalysisAssert(pnodeFnc); *m_ppnodeVar = nullptr; m_ppnodeVar = ppnodeVarSave; if (m_currentNodeFunc && (pnodeFnc->CallsEval() || pnodeFnc->ChildCallsEval())) { GetCurrentFunctionNode()->SetChildCallsEval(true); } // Lambdas do not have "arguments" and instead capture their parent's // binding of "arguments. To ensure the arguments object of the enclosing // non-lambda function is loaded propagate the UsesArguments flag up to // the parent function if (fLambda && (pnodeFnc->UsesArguments() || pnodeFnc->CallsEval())) { ParseNodeFnc * pnodeFncParent = GetCurrentFunctionNode(); if (pnodeFncParent != nullptr) { pnodeFncParent->SetUsesArguments(); } else { m_UsesArgumentsAtGlobal = true; } } if (needScanRCurly && !fModule) { // Consume the next token now that we're back in the enclosing function (whose strictness may be // different from the function we just finished). #if DBG bool expectedTokenValid = m_token.tk == tkRCurly; AssertMsg(expectedTokenValid, "Invalid token expected for RCurly match"); #endif // The next token may need to have a PID created in !buildAST mode, as we may be parsing a method with a string name. if (needsPIDOnRCurlyScan) { this->GetScanner()->ScanForcingPid(); } else { this->GetScanner()->Scan(); } } m_pnestedCount = pnestedCountSave; Assert(!buildAST || !wasInDeferredNestedFunc); m_inDeferredNestedFunc = wasInDeferredNestedFunc; if (this->m_arrayDepth) { this->m_funcInArrayDepth--; if (this->m_funcInArrayDepth == 0) { // We disable deferred parsing if array literals dominate. // But don't do this if the array literal is dominated by function bodies. if (flags & (fFncMethod | fFncClassMember) && m_token.tk != tkSColon) { // Class member methods have optional separators. We need to check whether we are // getting the IchLim of the correct token. Assert(this->GetScanner()->m_tkPrevious == tkRCurly && needScanRCurly); this->m_funcInArray += this->GetScanner()->IchMinTok() - /*tkRCurly*/ 1 - ichMin; } else { this->m_funcInArray += this->GetScanner()->IchLimTok() - ichMin; } } } m_scopeCountNoAst = scopeCountNoAstSave; if (fDeclaration && !IsStrictMode()) { if (pFncNamePid != nullptr && GetCurrentBlock() && GetCurrentBlock()->blockType == PnodeBlockType::Regular) { // Add a function-scoped VarDecl with the same name as the function for // back compat with pre-ES6 code that declares functions in blocks. The // idea is that the last executed declaration wins at the function scope // level and we accomplish this by having each block scoped function // declaration assign to both the block scoped "let" binding, as well // as the function scoped "var" binding. ParseNodeVar * vardecl = CreateVarDeclNode(pFncNamePid, STVariable, false, nullptr, false); vardecl->isBlockScopeFncDeclVar = true; if (GetCurrentFunctionNode() && vardecl->sym->GetIsFormal()) { GetCurrentFunctionNode()->SetHasAnyWriteToFormals(true); } } } if (buildAST && fDeclaration) { Symbol* funcSym = pnodeFnc->GetFuncSymbol(); if (funcSym->GetIsFormal()) { GetCurrentFunctionNode()->SetHasAnyWriteToFormals(true); } } this->m_tryCatchOrFinallyDepth = tryCatchOrFinallyDepthSave; return pnodeFnc; } bool Parser::FncDeclAllowedWithoutContext(ushort flags) { // Statement context required for strict mode, async functions, and generators. // Note that generators aren't detected yet when this method is called; they're checked elsewhere. return !IsStrictMode() && !(flags & fFncAsync); } uint Parser::CalculateFunctionColumnNumber() { uint columnNumber; charcount_t ichMinTok = this->GetScanner()->IchMinTok(); charcount_t ichMinLine = this->GetScanner()->IchMinLine(); if (ichMinTok >= ichMinLine) { // In scenarios involving defer parse IchMinLine() can be incorrect for the first line after defer parse columnNumber = ichMinTok - ichMinLine; if (m_functionBody != nullptr && m_functionBody->GetRelativeLineNumber() == this->GetScanner()->LineCur()) { // Adjust the column if it falls on the first line, where the re-parse is happening. columnNumber += m_functionBody->GetRelativeColumnNumber(); } } else if (m_currentNodeFunc) { // For the first line after defer parse, compute the column relative to the column number // of the lexically parent function. ULONG offsetFromCurrentFunction = ichMinTok - m_currentNodeFunc->ichMin; columnNumber = m_currentNodeFunc->columnNumber + offsetFromCurrentFunction; } else { // if there is no current function, lets give a default of 0. columnNumber = 0; } return columnNumber; } void Parser::AppendFunctionToScopeList(bool fDeclaration, ParseNodeFnc * pnodeFnc) { if (!fDeclaration && m_ppnodeExprScope) { // We're tracking function expressions separately from declarations in this scope // (e.g., inside a catch scope in standards mode). Assert(*m_ppnodeExprScope == nullptr); *m_ppnodeExprScope = pnodeFnc; m_ppnodeExprScope = &pnodeFnc->pnodeNext; } else { Assert(*m_ppnodeScope == nullptr); *m_ppnodeScope = pnodeFnc; m_ppnodeScope = &pnodeFnc->pnodeNext; } } /*************************************************************************** Parse a function definition. ***************************************************************************/ template void Parser::ParseFncDeclHelper(ParseNodeFnc * pnodeFnc, LPCOLESTR pNameHint, ushort flags, bool fUnaryOrParen, bool noStmtContext, bool *pNeedScanRCurly, bool skipFormals, IdentPtr* pFncNamePid, bool fAllowIn) { Assert(pnodeFnc); ParseNodeFnc * pnodeFncParent = GetCurrentFunctionNode(); // is the following correct? When buildAST is false, m_currentNodeDeferredFunc can be nullptr on transition to deferred parse from non-deferred ParseNodeFnc * pnodeFncSave = buildAST ? m_currentNodeFunc : m_currentNodeDeferredFunc; ParseNodeFnc * pnodeFncSaveNonLambda = buildAST ? m_currentNodeNonLambdaFunc : m_currentNodeNonLambdaDeferredFunc; int32* pAstSizeSave = m_pCurrentAstSize; bool fDeclaration = (flags & fFncDeclaration) != 0; bool fLambda = (flags & fFncLambda) != 0; bool fAsync = (flags & fFncAsync) != 0; bool fModule = (flags & fFncModule) != 0; bool fDeferred = false; StmtNest *pstmtSave; bool fFunctionInBlock = false; if (buildAST) { fFunctionInBlock = GetCurrentBlockInfo() != GetCurrentFunctionBlockInfo() && (GetCurrentBlockInfo()->pnodeBlock->scope == nullptr || GetCurrentBlockInfo()->pnodeBlock->scope->GetScopeType() != ScopeType_GlobalEvalBlock); } // Save the position of the scanner in case we need to inspect the name hint later RestorePoint beginNameHint; this->GetScanner()->Capture(&beginNameHint); ParseNodeBlock * pnodeFncExprScope = nullptr; Scope *fncExprScope = nullptr; if (!fDeclaration) { if (!fLambda) { pnodeFncExprScope = StartParseBlock(PnodeBlockType::Function, ScopeType_FuncExpr); fncExprScope = pnodeFncExprScope->scope; } // Function expression: push the new function onto the stack now so that the name (if any) will be // local to the new function. this->UpdateCurrentNodeFunc(pnodeFnc, fLambda); } if (!fLambda && !fModule) { this->ParseFncName(pnodeFnc, flags, pFncNamePid); } if (fDeclaration) { // Declaration statement: push the new function now, after parsing the name, so the name is local to the // enclosing function. this->UpdateCurrentNodeFunc(pnodeFnc, fLambda); } if (noStmtContext && pnodeFnc->IsGenerator()) { // Generator decl not allowed outside stmt context. (We have to wait until we've parsed the '*' to // detect generator.) Error(ERRsyntax, pnodeFnc); } // switch scanner to treat 'yield' as keyword in generator functions // or as an identifier in non-generator functions bool fPreviousYieldIsKeyword = this->GetScanner()->SetYieldIsKeywordRegion(pnodeFnc->IsGenerator()); bool fPreviousAwaitIsKeyword = this->GetScanner()->SetAwaitIsKeywordRegion(fAsync); if (pnodeFnc->IsGenerator()) { CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, Generator, m_scriptContext); } if (fncExprScope && pnodeFnc->pnodeName == nullptr) { FinishParseBlock(pnodeFncExprScope); m_nextBlockId--; Adelete(&m_nodeAllocator, fncExprScope); fncExprScope = nullptr; pnodeFncExprScope = nullptr; } pnodeFnc->scope = fncExprScope; // Start a new statement stack. bool topLevelStmt = buildAST && !fFunctionInBlock && (this->m_pstmtCur == nullptr || this->m_pstmtCur->pnodeStmt->nop == knopBlock); pstmtSave = m_pstmtCur; SetCurrentStatement(nullptr); RestorePoint beginFormals; this->GetScanner()->Capture(&beginFormals); BOOL fWasAlreadyStrictMode = IsStrictMode(); BOOL oldStrictMode = this->m_fUseStrictMode; if (fLambda) { CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, Lambda, m_scriptContext); } uint uCanDeferSave = m_grfscr & fscrCanDeferFncParse; uint uDeferSave = m_grfscr & fscrWillDeferFncParse; if (flags & fFncClassMember) { // Disable deferral on class members or other construct with unusual text bounds // as these are usually trivial, and re-parsing is problematic. // NOTE: It is probably worth supporting these cases for memory and load-time purposes, // especially as they become more and more common. m_grfscr &= ~(fscrCanDeferFncParse | fscrWillDeferFncParse); } bool isTopLevelDeferredFunc = false; #if ENABLE_BACKGROUND_PARSING struct AutoFastScanFlag { bool savedDoingFastScan; AutoFastScanFlag(Parser *parser) : m_parser(parser) { savedDoingFastScan = m_parser->m_doingFastScan; } ~AutoFastScanFlag() { m_parser->m_doingFastScan = savedDoingFastScan; } Parser *m_parser; } flag(this); #endif bool doParallel = false; #if ENABLE_BACKGROUND_PARSING bool parallelJobStarted = false; #endif if (buildAST) { bool isLikelyIIFE = !fDeclaration && fUnaryOrParen; BOOL isDeferredFnc = IsDeferredFnc(); // These are the conditions that prohibit upfront deferral *and* redeferral. isTopLevelDeferredFunc = (m_grfscr & fscrCanDeferFncParse) && !m_InAsmMode // Don't defer a module function wrapper because we need to do export resolution at parse time && !fModule; pnodeFnc->SetCanBeDeferred(isTopLevelDeferredFunc && ParseNodeFnc::CanBeRedeferred(pnodeFnc->fncFlags)); // These are heuristic conditions that prohibit upfront deferral but not redeferral. isTopLevelDeferredFunc = isTopLevelDeferredFunc && !isDeferredFnc && WillDeferParse(pnodeFnc->functionId) && (!isLikelyIIFE || !topLevelStmt || PHASE_FORCE_RAW(Js::DeferParsePhase, m_sourceContextInfo->sourceContextId, pnodeFnc->functionId)); #if ENABLE_BACKGROUND_PARSING if (!fLambda && !isDeferredFnc && !isLikelyIIFE && !this->IsBackgroundParser() && !this->m_doingFastScan && !(pnodeFncSave && m_currDeferredStub) && !(this->m_parseType == ParseType_Deferred && this->m_functionBody && this->m_functionBody->GetScopeInfo() && !isTopLevelDeferredFunc)) { doParallel = DoParallelParse(pnodeFnc); if (doParallel) { BackgroundParser *bgp = m_scriptContext->GetBackgroundParser(); Assert(bgp); if (bgp->HasFailedBackgroundParseItem()) { Error(ERRsyntax); } doParallel = bgp->ParseBackgroundItem(this, pnodeFnc, isTopLevelDeferredFunc); if (doParallel) { parallelJobStarted = true; this->m_hasParallelJob = true; this->m_doingFastScan = true; doParallel = FastScanFormalsAndBody(); if (doParallel) { // Let the foreground thread take care of marking the limit on the function node, // because in some cases this function's caller will want to change that limit, // so we don't want the background thread to try and touch it. pnodeFnc->ichLim = this->GetScanner()->IchLimTok(); pnodeFnc->cbLim = this->GetScanner()->IecpLimTok(); } } } } #endif } if (!doParallel) { #if ENABLE_BACKGROUND_PARSING // We don't want to, or couldn't, let the main thread scan past this function body, so parse // it for real. ParseNodeFnc * pnodeRealFnc = pnodeFnc; if (parallelJobStarted) { // We have to deal with a failure to fast-scan the function (due to syntax error? "/"?) when // a background thread may already have begun to work on the job. Both threads can't be allowed to // operate on the same node. pnodeFnc = CreateDummyFuncNode(fDeclaration); } #endif AnalysisAssert(pnodeFnc); ParseNodeBlock * pnodeBlock = StartParseBlock(PnodeBlockType::Parameter, ScopeType_Parameter); AnalysisAssert(pnodeBlock != nullptr); pnodeFnc->pnodeScopes = pnodeBlock; m_ppnodeVar = &pnodeFnc->pnodeParams; pnodeFnc->pnodeVars = nullptr; ParseNodePtr* varNodesList = &pnodeFnc->pnodeVars; ParseNodeVar * argNode = nullptr; if (!fModule && !fLambda) { ParseNodePtr *const ppnodeVarSave = m_ppnodeVar; m_ppnodeVar = &pnodeFnc->pnodeVars; // Create the built-in arguments symbol argNode = this->AddArgumentsNodeToVars(pnodeFnc); // Save the updated var list varNodesList = m_ppnodeVar; m_ppnodeVar = ppnodeVarSave; } ParseNodePtr *ppnodeScopeSave = nullptr; ParseNodePtr *ppnodeExprScopeSave = nullptr; ppnodeScopeSave = m_ppnodeScope; if (pnodeBlock) { // This synthetic block scope will contain all the nested scopes. m_ppnodeScope = &pnodeBlock->pnodeScopes; pnodeBlock->pnodeStmt = pnodeFnc; } // Keep nested function declarations and expressions in the same list at function scope. // (Indicate this by nulling out the current function expressions list.) ppnodeExprScopeSave = m_ppnodeExprScope; m_ppnodeExprScope = nullptr; uint parenExprDepthSave = m_funcParenExprDepth; m_funcParenExprDepth = 0; if (!skipFormals) { bool fLambdaParamsSave = m_reparsingLambdaParams; if (fLambda) { m_reparsingLambdaParams = true; } uint savedStubCount = m_currDeferredStubCount; DeferredFunctionStub* savedStub = m_currDeferredStub; ShiftCurrDeferredStubToChildFunction(pnodeFnc, pnodeFncParent); this->ParseFncFormals(pnodeFnc, pnodeFncParent, flags, isTopLevelDeferredFunc); m_currDeferredStub = savedStub; m_currDeferredStubCount = savedStubCount; m_reparsingLambdaParams = fLambdaParamsSave; } // Create function body scope ParseNodeBlock * pnodeInnerBlock = StartParseBlock(PnodeBlockType::Function, ScopeType_FunctionBody); // Set the parameter block's child to the function body block. // The pnodeFnc->pnodeScopes list is constructed in such a way that it includes all the scopes in this list. // For example if the param scope has one function and body scope has one function then the list will look like below, // param scope block -> function decl from param scope -> body socpe block -> function decl from body scope. *m_ppnodeScope = pnodeInnerBlock; pnodeFnc->pnodeBodyScope = pnodeInnerBlock; // This synthetic block scope will contain all the nested scopes. m_ppnodeScope = &pnodeInnerBlock->pnodeScopes; pnodeInnerBlock->pnodeStmt = pnodeFnc; // DEFER: Begin deferral here (after names are parsed and name nodes created). // Create no more AST nodes until we're done. // Try to defer this func if all these are true: // 0. We are not already in deferred parsing (i.e. buildAST is true) // 1. We are not re-parsing a deferred func which is being invoked. // 2. Dynamic profile suggests this func can be deferred (and deferred parse is on). // 3. This func is top level or defer nested func is on. // 4. Optionally, the function is non-nested and not in eval, or the deferral decision was based on cached profile info, // or the function is sufficiently long. (I.e., don't defer little nested functions unless we're // confident they'll never be executed, because un-deferring nested functions is more expensive.) // NOTE: I'm disabling #4 by default, because we've found other ways to reduce the cost of un-deferral, // and we don't want to create function bodies aggressively for little functions. // We will also temporarily defer all asm.js functions, except for the asm.js // module itself, which we will never defer bool strictModeTurnedOn = false; if (isTopLevelDeferredFunc && !(this->m_grfscr & fscrEvalCode) && pnodeFnc->IsNested() && #ifndef DISABLE_DYNAMIC_PROFILE_DEFER_PARSE m_sourceContextInfo->sourceDynamicProfileManager == nullptr && #endif PHASE_ON_RAW(Js::ScanAheadPhase, m_sourceContextInfo->sourceContextId, pnodeFnc->functionId) && ( !PHASE_FORCE_RAW(Js::DeferParsePhase, m_sourceContextInfo->sourceContextId, pnodeFnc->functionId) || PHASE_FORCE_RAW(Js::ScanAheadPhase, m_sourceContextInfo->sourceContextId, pnodeFnc->functionId) )) { // Try to scan ahead to the end of the function. If we get there before we've scanned a minimum // number of tokens, don't bother deferring, because it's too small. if (this->ScanAheadToFunctionEnd(CONFIG_FLAG(MinDeferredFuncTokenCount))) { isTopLevelDeferredFunc = false; } } Scope* paramScope = pnodeFnc->pnodeScopes ? pnodeFnc->pnodeScopes->scope : nullptr; if (paramScope != nullptr) { if (CONFIG_FLAG(ForceSplitScope)) { pnodeFnc->ResetBodyAndParamScopeMerged(); } else if (pnodeFnc->HasNonSimpleParameterList() && pnodeFnc->IsBodyAndParamScopeMerged()) { paramScope->ForEachSymbolUntil([this, paramScope, pnodeFnc](Symbol* sym) { if (sym->GetPid()->GetTopRef()->GetFuncScopeId() > pnodeFnc->functionId) { // One of the symbol has non local reference. Mark the param scope as we can't merge it with body scope. pnodeFnc->ResetBodyAndParamScopeMerged(); return true; } return false; }); if (pnodeFnc->IsBodyAndParamScopeMerged() && !fDeclaration && pnodeFnc->pnodeName != nullptr) { Assert(pnodeFnc->pnodeName->nop == knopVarDecl); Symbol* funcSym = pnodeFnc->pnodeName->sym; if (funcSym->GetPid()->GetTopRef()->GetFuncScopeId() > pnodeFnc->functionId) { // This is a function expression with name captured in the param scope. In non-eval, non-split cases the function // name symbol is added to the body scope to make it accessible in the body. But if there is a function or var // declaration with the same name in the body then adding to the body will fail. So in this case we have to add // the name symbol to the param scope by splitting it. pnodeFnc->ResetBodyAndParamScopeMerged(); } } } } // If the param scope is merged with the body scope we want to use the param scope symbols in the body scope. // So add a pid ref for the body using the param scope symbol. Note that in this case the same symbol will occur twice // in the same pid ref stack. if (paramScope != nullptr && pnodeFnc->IsBodyAndParamScopeMerged()) { paramScope->ForEachSymbol([this](Symbol* paramSym) { PidRefStack* ref = PushPidRef(paramSym->GetPid()); ref->SetSym(paramSym); }); } AssertMsg(m_funcParenExprDepth == 0, "Paren exprs should have been resolved by the time we finish function formals"); if (fLambda) { #ifdef ASMJS_PLAT if (m_InAsmMode && (isTopLevelDeferredFunc && m_deferAsmJs)) { // asm.js doesn't support lambda functions Js::AsmJSCompiler::OutputError(m_scriptContext, _u("Lambda functions are not supported.")); Js::AsmJSCompiler::OutputError(m_scriptContext, _u("Asm.js compilation failed.")); throw Js::AsmJsParseException(); } #endif } if (m_token.tk == tkRParen) { this->GetScanner()->Scan(); } if (fLambda) { BOOL hadNewLine = this->GetScanner()->FHadNewLine(); // it can be the case we do not have a fat arrow here if there is a valid expression on the left hand side // of the fat arrow, but that expression does not parse as a parameter list. E.g. // a.x => { } // Therefore check for it and error if not found. ChkCurTok(tkDArrow, ERRnoDArrow); // Newline character between arrow parameters and fat arrow is a syntax error but we want to check for // this after verifying there was a => token. Otherwise we would throw the wrong error. if (hadNewLine) { Error(ERRsyntax); } } if (isTopLevelDeferredFunc || (m_InAsmMode && m_deferAsmJs)) { fDeferred = true; this->ParseTopLevelDeferredFunc(pnodeFnc, pnodeFncSave, pNameHint, fLambda, pNeedScanRCurly, fAllowIn); } else { AnalysisAssert(pnodeFnc); // Shouldn't be any temps in the arg list. Assert(*m_ppnodeVar == nullptr); // Start the var list. m_ppnodeVar = varNodesList; if (!pnodeFnc->IsBodyAndParamScopeMerged()) { OUTPUT_TRACE_DEBUGONLY(Js::ParsePhase, _u("The param and body scope of the function %s cannot be merged\n"), pnodeFnc->pnodeName ? pnodeFnc->pnodeName->pid->Psz() : _u("Anonymous function")); } // Keep nested function declarations and expressions in the same list at function scope. // (Indicate this by nulling out the current function expressions list.) m_ppnodeExprScope = nullptr; if (buildAST) { if (m_token.tk != tkLCurly && fLambda) { *pNeedScanRCurly = false; } uint savedStubCount = m_currDeferredStubCount; DeferredFunctionStub* savedStub = m_currDeferredStub; ShiftCurrDeferredStubToChildFunction(pnodeFnc, pnodeFncSave); this->FinishFncDecl(pnodeFnc, pNameHint, fLambda, skipFormals, fAllowIn); m_currDeferredStub = savedStub; m_currDeferredStubCount = savedStubCount; } else { this->ParseNestedDeferredFunc(pnodeFnc, fLambda, pNeedScanRCurly, &strictModeTurnedOn, fAllowIn); } } // Restore the paren count for any outer spread/rest error checking. m_funcParenExprDepth = parenExprDepthSave; if (pnodeInnerBlock) { FinishParseBlock(pnodeInnerBlock, *pNeedScanRCurly); } if (!fModule && (m_token.tk == tkLCurly || !fLambda)) { UpdateArgumentsNode(pnodeFnc, argNode); } CreateSpecialSymbolDeclarations(pnodeFnc); // Restore the lists of scopes that contain function expressions. Assert(m_ppnodeExprScope == nullptr || *m_ppnodeExprScope == nullptr); m_ppnodeExprScope = ppnodeExprScopeSave; Assert(m_ppnodeScope); Assert(nullptr == *m_ppnodeScope); m_ppnodeScope = ppnodeScopeSave; if (pnodeBlock) { FinishParseBlock(pnodeBlock, *pNeedScanRCurly); } if (IsStrictMode() || strictModeTurnedOn) { this->m_fUseStrictMode = TRUE; // Now we know this function is in strict mode if (!fWasAlreadyStrictMode) { // If this function turned on strict mode then we didn't check the formal // parameters or function name hint for future reserved word usage. So do that now. RestorePoint afterFnc; this->GetScanner()->Capture(&afterFnc); if (pnodeFnc->pnodeName != nullptr) { // Rewind to the function name hint and check if the token is a reserved word. this->GetScanner()->SeekTo(beginNameHint); this->GetScanner()->Scan(); if (pnodeFnc->IsGenerator()) { Assert(m_token.tk == tkStar); Assert(m_scriptContext->GetConfig()->IsES6GeneratorsEnabled()); Assert(!(flags & fFncClassMember)); this->GetScanner()->Scan(); } if (m_token.IsReservedWord()) { IdentifierExpectedError(m_token); } CheckStrictModeEvalArgumentsUsage(m_token.GetIdentifier(this->GetHashTbl())); } // Fast forward to formal parameter list, check for future reserved words, // then restore scanner as it was. this->GetScanner()->SeekToForcingPid(beginFormals); CheckStrictFormalParameters(); this->GetScanner()->SeekTo(afterFnc); } if (buildAST) { if (pnodeFnc->pnodeName != nullptr) { Assert(pnodeFnc->pnodeName->nop == knopVarDecl); CheckStrictModeEvalArgumentsUsage(pnodeFnc->pnodeName->pid, pnodeFnc->pnodeName); } } this->m_fUseStrictMode = oldStrictMode; CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, StrictModeFunction, m_scriptContext); } ProcessCapturedNames(pnodeFnc); if (fDeferred) { AnalysisAssert(pnodeFnc); pnodeFnc->pnodeVars = nullptr; } #if ENABLE_BACKGROUND_PARSING if (parallelJobStarted) { pnodeFnc = pnodeRealFnc; m_currentNodeFunc = pnodeRealFnc; // Let the foreground thread take care of marking the limit on the function node, // because in some cases this function's caller will want to change that limit, // so we don't want the background thread to try and touch it. pnodeFnc->ichLim = this->GetScanner()->IchLimTok(); pnodeFnc->cbLim = this->GetScanner()->IecpLimTok(); } #endif } // after parsing asm.js module, we want to reset asm.js state before continuing AnalysisAssert(pnodeFnc); if (pnodeFnc->GetAsmjsMode()) { m_InAsmMode = false; } // Restore the statement stack. Assert(nullptr == m_pstmtCur); SetCurrentStatement(pstmtSave); if (pnodeFncExprScope) { FinishParseFncExprScope(pnodeFnc, pnodeFncExprScope); } m_grfscr |= uCanDeferSave; if (!m_stoppedDeferredParse) { m_grfscr |= uDeferSave; } this->GetScanner()->SetYieldIsKeywordRegion(fPreviousYieldIsKeyword); this->GetScanner()->SetAwaitIsKeywordRegion(fPreviousAwaitIsKeyword); // Restore the current function. if (buildAST) { Assert(pnodeFnc == m_currentNodeFunc); m_currentNodeFunc = pnodeFncSave; m_pCurrentAstSize = pAstSizeSave; if (!fLambda) { Assert(pnodeFnc == m_currentNodeNonLambdaFunc); m_currentNodeNonLambdaFunc = pnodeFncSaveNonLambda; } } else { Assert(pnodeFnc == m_currentNodeDeferredFunc); if (!fLambda) { Assert(pnodeFnc == m_currentNodeNonLambdaDeferredFunc); m_currentNodeNonLambdaDeferredFunc = pnodeFncSaveNonLambda; } m_currentNodeDeferredFunc = pnodeFncSave; } if (m_currentNodeFunc && pnodeFnc->HasWithStmt()) { GetCurrentFunctionNode()->SetHasWithStmt(true); } } template void Parser::UpdateCurrentNodeFunc(ParseNodeFnc * pnodeFnc, bool fLambda) { if (buildAST) { // Make this the current function and start its sub-function list. m_currentNodeFunc = pnodeFnc; Assert(m_currentNodeDeferredFunc == nullptr); if (!fLambda) { m_currentNodeNonLambdaFunc = pnodeFnc; } } else // if !buildAST { AnalysisAssert(pnodeFnc); if (!fLambda) { m_currentNodeNonLambdaDeferredFunc = pnodeFnc; } m_currentNodeDeferredFunc = pnodeFnc; } } void Parser::ParseTopLevelDeferredFunc(ParseNodeFnc * pnodeFnc, ParseNodeFnc * pnodeFncParent, LPCOLESTR pNameHint, bool fLambda, bool *pNeedScanRCurly, bool fAllowIn) { // Parse a function body that is a transition point from building AST to doing fast syntax check. pnodeFnc->pnodeVars = nullptr; pnodeFnc->pnodeBody = nullptr; this->m_deferringAST = TRUE; // Put the scanner into "no hashing" mode. BYTE deferFlags = this->GetScanner()->SetDeferredParse(TRUE); if (!fLambda) { ChkCurTok(tkLCurly, ERRnoLcurly); } else { // Lambda may consist of a single expression instead of a block if (this->GetScanner()->m_ptoken->tk == tkLCurly) { this->GetScanner()->Scan(); } else { *pNeedScanRCurly = false; } } ParseNodePtr *ppnodeVarSave = m_ppnodeVar; m_ppnodeVar = &pnodeFnc->pnodeVars; // Don't try and skip scanning nested deferred lambdas which have only a single expression in the body. // Their more-complicated text extents won't match the deferred-stub and the single expression should be fast to scan, anyway. if (fLambda && !*pNeedScanRCurly) { ParseExpressionLambdaBody(pnodeFnc, fAllowIn); } else if (pnodeFncParent != nullptr && m_currDeferredStub != nullptr) { // We've already parsed this function body for syntax errors on the initial parse of the script. // We have information that allows us to skip it, so do so. Assert(pnodeFncParent->nestedCount != 0); DeferredFunctionStub *stub = m_currDeferredStub + (pnodeFncParent->nestedCount - 1); Assert(pnodeFnc->ichMin == stub->ichMin || (stub->fncFlags & kFunctionIsAsync) == kFunctionIsAsync || ((stub->fncFlags & kFunctionIsMethod) == kFunctionIsMethod && ( (stub->fncFlags & kFunctionIsAccessor) == kFunctionIsAccessor || (stub->fncFlags & kFunctionIsGenerator) == kFunctionIsGenerator || (stub->fncFlags & kFunctionHasComputedName) == kFunctionHasComputedName ))); if (stub->fncFlags & kFunctionCallsEval) { this->MarkEvalCaller(); } PHASE_PRINT_TRACE1( Js::SkipNestedDeferredPhase, _u("Skipping nested deferred function %d. %s: %d...%d\n"), pnodeFnc->functionId, GetFunctionName(pnodeFnc, pNameHint), pnodeFnc->ichMin, stub->restorePoint.m_ichMinTok); this->GetScanner()->SeekTo(stub->restorePoint, m_nextFunctionId); for (uint i = 0; i < stub->capturedNameCount; i++) { int stringId = stub->capturedNameSerializedIds[i]; uint32 stringLength = 0; LPCWSTR stringVal = Js::ByteCodeSerializer::DeserializeString(stub, stringId, stringLength); OUTPUT_TRACE_DEBUGONLY(Js::SkipNestedDeferredPhase, _u("\tPushing a reference to '%s'\n"), stringVal); IdentPtr pid = this->GetHashTbl()->PidHashNameLen(stringVal, stringLength); PushPidRef(pid); } pnodeFnc->nestedCount = stub->nestedCount; pnodeFnc->deferredStub = stub->deferredStubs; pnodeFnc->fncFlags = (FncFlags)(pnodeFnc->fncFlags | stub->fncFlags); } else { ParseStmtList(nullptr, nullptr, SM_DeferredParse, true /* isSourceElementList */); } if (!fLambda || *pNeedScanRCurly) { pnodeFnc->ichLim = this->GetScanner()->IchLimTok(); pnodeFnc->cbLim = this->GetScanner()->IecpLimTok(); } m_ppnodeVar = ppnodeVarSave; // Restore the scanner's default hashing mode. // Do this before we consume the next token. this->GetScanner()->SetDeferredParseFlags(deferFlags); if (*pNeedScanRCurly) { ChkCurTokNoScan(tkRCurly, ERRnoRcurly); } #if DBG pnodeFnc->deferredParseNextFunctionId = *this->m_nextFunctionId; #endif this->m_deferringAST = FALSE; } bool Parser::DoParallelParse(ParseNodeFnc * pnodeFnc) const { #if ENABLE_BACKGROUND_PARSING if (!PHASE_ENABLED_RAW(ParallelParsePhase, m_sourceContextInfo->sourceContextId, pnodeFnc->functionId)) { return false; } BackgroundParser *bgp = m_scriptContext->GetBackgroundParser(); return bgp != nullptr; #else return false; #endif } bool Parser::ScanAheadToFunctionEnd(uint count) { bool found = false; uint curlyDepth = 0; RestorePoint funcStart; this->GetScanner()->Capture(&funcStart); for (uint i = 0; i < count; i++) { switch (m_token.tk) { case tkStrTmplBegin: case tkStrTmplMid: case tkStrTmplEnd: case tkDiv: case tkAsgDiv: case tkScanError: case tkEOF: goto LEnd; case tkLCurly: UInt32Math::Inc(curlyDepth, Parser::OutOfMemory); break; case tkRCurly: if (curlyDepth == 1) { found = true; goto LEnd; } if (curlyDepth == 0) { goto LEnd; } curlyDepth--; break; } this->GetScanner()->ScanAhead(); } LEnd: this->GetScanner()->SeekTo(funcStart); return found; } #if ENABLE_BACKGROUND_PARSING bool Parser::FastScanFormalsAndBody() { // The scanner is currently pointing just past the name of a function. // The idea here is to find the end of the function body as quickly as possible, // by tokenizing and tracking {}'s if possible. // String templates require some extra logic but can be handled. // The real wrinkle is "/" and "/=", which may indicate either a RegExp literal or a division, depending // on the context. // To handle this with minimal work, keep track of the last ";" seen at each {} depth. If we see one of the // difficult tokens, rewind to the last ";" at the current {} depth and parse statements until we pass the // point where we had to rewind. This will process the "/" as required. RestorePoint funcStart; this->GetScanner()->Capture(&funcStart); const int maxRestorePointDepth = 16; struct FastScanRestorePoint { RestorePoint restorePoint; uint parenDepth; Js::LocalFunctionId functionId; int blockId; FastScanRestorePoint() : restorePoint(), parenDepth(0) {}; }; FastScanRestorePoint lastSColonAtCurlyDepth[maxRestorePointDepth]; charcount_t ichStart = this->GetScanner()->IchMinTok(); uint blockIdSave = m_nextBlockId; uint functionIdSave = *m_nextFunctionId; uint curlyDepth = 0; uint strTmplDepth = 0; for (;;) { switch (m_token.tk) { case tkStrTmplBegin: UInt32Math::Inc(strTmplDepth, Parser::OutOfMemory); // Fall through case tkStrTmplMid: case tkLCurly: UInt32Math::Inc(curlyDepth, Parser::OutOfMemory); Int32Math::Inc(m_nextBlockId, &m_nextBlockId); break; case tkStrTmplEnd: // We can assert here, because the scanner will only return this token if we've told it we're // in a string template. Assert(strTmplDepth > 0); strTmplDepth--; break; case tkRCurly: if (curlyDepth == 1) { Assert(strTmplDepth == 0); if (PHASE_TRACE1(Js::ParallelParsePhase)) { Output::Print(_u("Finished fast seek: %d. %s -- %d...%d\n"), m_currentNodeFunc->functionId, GetFunctionName(m_currentNodeFunc, m_currentNodeFunc->hint), ichStart, this->GetScanner()->IchLimTok()); } return true; } if (curlyDepth < maxRestorePointDepth) { lastSColonAtCurlyDepth[curlyDepth].restorePoint.m_ichMinTok = (uint)-1; } curlyDepth--; if (strTmplDepth > 0) { this->GetScanner()->SetScanState(Scanner_t::ScanState::ScanStateStringTemplateMiddleOrEnd); } break; case tkSColon: // Track the location of the ";" (if it's outside parens, as we don't, for instance, want // to track the ";"'s in a for-loop header. If we find it's important to rewind within a paren // expression, we can do something more sophisticated.) if (curlyDepth < maxRestorePointDepth && lastSColonAtCurlyDepth[curlyDepth].parenDepth == 0) { this->GetScanner()->Capture(&lastSColonAtCurlyDepth[curlyDepth].restorePoint); lastSColonAtCurlyDepth[curlyDepth].functionId = *this->m_nextFunctionId; lastSColonAtCurlyDepth[curlyDepth].blockId = m_nextBlockId; } break; case tkLParen: if (curlyDepth < maxRestorePointDepth) { UInt32Math::Inc(lastSColonAtCurlyDepth[curlyDepth].parenDepth); } break; case tkRParen: if (curlyDepth < maxRestorePointDepth) { Assert(lastSColonAtCurlyDepth[curlyDepth].parenDepth != 0); lastSColonAtCurlyDepth[curlyDepth].parenDepth--; } break; case tkID: { charcount_t tokLength = this->GetScanner()->IchLimTok() - this->GetScanner()->IchMinTok(); // Detect the function and class keywords so we can track function ID's. // (In fast mode, the scanner doesn't distinguish keywords and doesn't point the token // to a PID.) // Detect try/catch/for to increment block count for them. switch (tokLength) { case 3: if (!memcmp(this->GetScanner()->PchMinTok(), "try", 3) || !memcmp(this->GetScanner()->PchMinTok(), "for", 3)) { Int32Math::Inc(m_nextBlockId, &m_nextBlockId); } break; case 5: if (!memcmp(this->GetScanner()->PchMinTok(), "catch", 5)) { Int32Math::Inc(m_nextBlockId, &m_nextBlockId); } else if (!memcmp(this->GetScanner()->PchMinTok(), "class", 5)) { Int32Math::Inc(m_nextBlockId, &m_nextBlockId); Int32Math::Inc(*this->m_nextFunctionId, (int*)this->m_nextFunctionId); } break; case 8: if (!memcmp(this->GetScanner()->PchMinTok(), "function", 8)) { // Account for the possible func expr scope or dummy block for missing {}'s around a declaration Int32Math::Inc(m_nextBlockId, &m_nextBlockId); Int32Math::Inc(*this->m_nextFunctionId, (int*)this->m_nextFunctionId); } break; } break; } case tkDArrow: Int32Math::Inc(m_nextBlockId, &m_nextBlockId); Int32Math::Inc(*this->m_nextFunctionId, (int*)this->m_nextFunctionId); break; case tkDiv: case tkAsgDiv: { int opl; OpCode nop; tokens tkPrev = this->GetScanner()->m_tkPrevious; if ((this->GetHashTbl()->TokIsBinop(tkPrev, &opl, &nop) && nop != knopNone) || (this->GetHashTbl()->TokIsUnop(tkPrev, &opl, &nop) && nop != knopNone && tkPrev != tkInc && tkPrev != tkDec) || tkPrev == tkColon || tkPrev == tkLParen || tkPrev == tkLBrack || tkPrev == tkRETURN) { // Previous token indicates that we're starting an expression here and can't have a // binary operator now. // Assume this is a RegExp. ParseRegExp(); break; } uint tempCurlyDepth = curlyDepth < maxRestorePointDepth ? curlyDepth : maxRestorePointDepth - 1; for (; tempCurlyDepth != (uint)-1; tempCurlyDepth--) { // We don't know whether we've got a RegExp or a divide. Rewind to the last safe ";" // if we can and parse statements until we pass this point. if (lastSColonAtCurlyDepth[tempCurlyDepth].restorePoint.m_ichMinTok != -1) { break; } } if (tempCurlyDepth != (uint)-1) { ParseNodeFnc * pnodeFncSave = m_currentNodeFunc; int32 *pastSizeSave = m_pCurrentAstSize; uint *pnestedCountSave = m_pnestedCount; ParseNodePtr *ppnodeScopeSave = m_ppnodeScope; ParseNodePtr *ppnodeExprScopeSave = m_ppnodeExprScope; ParseNodeFnc * pnodeFnc = CreateDummyFuncNode(true); charcount_t ichStop = this->GetScanner()->IchLimTok(); curlyDepth = tempCurlyDepth; this->GetScanner()->SeekTo(lastSColonAtCurlyDepth[tempCurlyDepth].restorePoint); m_nextBlockId = lastSColonAtCurlyDepth[tempCurlyDepth].blockId; *this->m_nextFunctionId = lastSColonAtCurlyDepth[tempCurlyDepth].functionId; ParseNodeBlock * pnodeBlock = StartParseBlock(PnodeBlockType::Function, ScopeType_FunctionBody); pnodeFnc->pnodeScopes = pnodeBlock; m_ppnodeScope = &pnodeBlock->pnodeScopes; m_ppnodeExprScope = nullptr; this->GetScanner()->Scan(); do { ParseStatement(); } while (this->GetScanner()->IchMinTok() < ichStop); FinishParseBlock(pnodeBlock); m_currentNodeFunc = pnodeFncSave; m_pCurrentAstSize = pastSizeSave; m_pnestedCount = pnestedCountSave; m_ppnodeScope = ppnodeScopeSave; m_ppnodeExprScope = ppnodeExprScopeSave; // We've already consumed the first token of the next statement, so just continue // without a further scan. continue; } } // fall through to rewind to function start case tkScanError: case tkEOF: // Unexpected token. if (PHASE_TRACE1(Js::ParallelParsePhase)) { Output::Print(_u("Failed fast seek: %d. %s -- %d...%d\n"), m_currentNodeFunc->functionId, GetFunctionName(m_currentNodeFunc, m_currentNodeFunc->hint), ichStart, this->GetScanner()->IchLimTok()); } m_nextBlockId = blockIdSave; *m_nextFunctionId = functionIdSave; this->GetScanner()->SeekTo(funcStart); return false; } this->GetScanner()->ScanNoKeywords(); } } #endif ParseNodeFnc * Parser::CreateDummyFuncNode(bool fDeclaration) { // Create a dummy node and make it look like the current function declaration. // Do this in situations where we want to parse statements without impacting // the state of the "real" AST. ParseNodeFnc * pnodeFnc = CreateAllowDeferNodeForOpT(); pnodeFnc->SetDeclaration(fDeclaration); pnodeFnc->SetNested(m_currentNodeFunc != nullptr); // If there is a current function, then we're a nested function. pnodeFnc->SetStrictMode(IsStrictMode()); // Inherit current strict mode -- may be overridden by the function itself if it contains a strict mode directive. m_pCurrentAstSize = &pnodeFnc->astSize; m_currentNodeFunc = pnodeFnc; m_pnestedCount = &pnodeFnc->nestedCount; return pnodeFnc; } void Parser::ParseNestedDeferredFunc(ParseNodeFnc * pnodeFnc, bool fLambda, bool *pNeedScanRCurly, bool *pStrictModeTurnedOn, bool fAllowIn) { // Parse a function nested inside another deferred function. size_t lengthBeforeBody = this->GetSourceLength(); if (m_token.tk != tkLCurly && fLambda) { ParseExpressionLambdaBody(pnodeFnc, fAllowIn); *pNeedScanRCurly = false; } else { ChkCurTok(tkLCurly, ERRnoLcurly); bool* detectStrictModeOn = IsStrictMode() ? nullptr : pStrictModeTurnedOn; m_ppnodeVar = &m_currentNodeDeferredFunc->pnodeVars; ParseStmtList(nullptr, nullptr, SM_DeferredParse, true /* isSourceElementList */, detectStrictModeOn); ChkCurTokNoScan(tkRCurly, ERRnoRcurly); pnodeFnc->ichLim = this->GetScanner()->IchLimTok(); pnodeFnc->cbLim = this->GetScanner()->IecpLimTok(); } if (*pStrictModeTurnedOn) { pnodeFnc->SetStrictMode(true); } if (!PHASE_OFF1(Js::SkipNestedDeferredPhase)) { // Record the end of the function and the function ID increment that happens inside the function. // Byte code gen will use this to build stub information to allow us to skip this function when the // enclosing function is fully parsed. RestorePoint *restorePoint = Anew(&m_nodeAllocator, RestorePoint); this->GetScanner()->Capture(restorePoint, *m_nextFunctionId - pnodeFnc->functionId - 1, lengthBeforeBody - this->GetSourceLength()); pnodeFnc->pRestorePoint = restorePoint; } } template void Parser::ParseFncName(ParseNodeFnc * pnodeFnc, ushort flags, IdentPtr* pFncNamePid) { Assert(pnodeFnc); BOOL fDeclaration = flags & fFncDeclaration; BOOL fIsAsync = flags & fFncAsync; this->GetScanner()->Scan(); // If generators are enabled then we are in a recent enough version // that deferred parsing will create a parse node for pnodeFnc and // it is safe to assume it is not null. if (flags & fFncGenerator) { Assert(m_scriptContext->GetConfig()->IsES6GeneratorsEnabled()); pnodeFnc->SetIsGenerator(); } else if (m_scriptContext->GetConfig()->IsES6GeneratorsEnabled() && m_token.tk == tkStar && !(flags & fFncClassMember)) { if (!fDeclaration) { bool fPreviousYieldIsKeyword = this->GetScanner()->SetYieldIsKeywordRegion(!fDeclaration); this->GetScanner()->Scan(); this->GetScanner()->SetYieldIsKeywordRegion(fPreviousYieldIsKeyword); } else { this->GetScanner()->Scan(); } pnodeFnc->SetIsGenerator(); } if (fIsAsync) { if (pnodeFnc->IsGenerator()) { Error(ERRsyntax); } pnodeFnc->SetIsAsync(); } pnodeFnc->pnodeName = nullptr; if ((m_token.tk != tkID || flags & fFncNoName) && (IsStrictMode() || fDeclaration || pnodeFnc->IsGenerator() || pnodeFnc->IsAsync() || (m_token.tk != tkYIELD && m_token.tk != tkAWAIT))) // Function expressions can have the name yield/await even inside generator/async functions { if (fDeclaration || m_token.IsReservedWord()) // For example: var x = (function break(){}); { IdentifierExpectedError(m_token); } return; } Assert(m_token.tk == tkID || (m_token.tk == tkYIELD && !fDeclaration) || (m_token.tk == tkAWAIT && !fDeclaration)); if (IsStrictMode()) { CheckStrictModeEvalArgumentsUsage(m_token.GetIdentifier(this->GetHashTbl())); } IdentPtr pidBase = m_token.GetIdentifier(this->GetHashTbl()); pnodeFnc->pnodeName = CreateDeclNode(knopVarDecl, pidBase, STFunction); pnodeFnc->pid = pnodeFnc->pnodeName->pid; if (pFncNamePid != nullptr) { *pFncNamePid = pidBase; } this->GetScanner()->Scan(); } void Parser::ValidateFormals() { ParseFncFormals(this->GetCurrentFunctionNode(), nullptr, fFncNoFlgs); // Eat the tkRParen. The ParseFncDeclHelper caller expects to see it. this->GetScanner()->Scan(); } void Parser::ValidateSourceElementList() { ParseStmtList(nullptr, nullptr, SM_NotUsed, true); } void Parser::UpdateOrCheckForDuplicateInFormals(IdentPtr pid, SList *formals) { bool isStrictMode = IsStrictMode(); if (isStrictMode) { CheckStrictModeEvalArgumentsUsage(pid); } if (formals->Has(pid)) { if (isStrictMode) { Error(ERRES5ArgSame); } else { Error(ERRFormalSame); } } else { formals->Prepend(pid); } } template void Parser::ParseFncFormals(ParseNodeFnc * pnodeFnc, ParseNodeFnc * pnodeParentFnc, ushort flags, bool isTopLevelDeferredFunc) { bool fLambda = (flags & fFncLambda) != 0; bool fMethod = (flags & fFncMethod) != 0; bool fNoArg = (flags & fFncNoArg) != 0; bool fOneArg = (flags & fFncOneArg) != 0; bool fAsync = (flags & fFncAsync) != 0; bool fPreviousYieldIsKeyword = false; bool fPreviousAwaitIsKeyword = false; if (fLambda) { fPreviousYieldIsKeyword = this->GetScanner()->SetYieldIsKeywordRegion(pnodeParentFnc != nullptr && pnodeParentFnc->IsGenerator()); fPreviousAwaitIsKeyword = this->GetScanner()->SetAwaitIsKeywordRegion(fAsync || (pnodeParentFnc != nullptr && pnodeParentFnc->IsAsync())); } Assert(!fNoArg || !fOneArg); // fNoArg and fOneArg can never be true at the same time. // strictFormals corresponds to the StrictFormalParameters grammar production // in the ES spec which just means duplicate names are not allowed bool fStrictFormals = IsStrictMode() || fLambda || fMethod; // When detecting duplicated formals pids are needed so force PID creation (unless the function should take 0 or 1 arg). bool forcePid = fStrictFormals && !fNoArg && !fOneArg; AutoTempForcePid autoForcePid(this->GetScanner(), forcePid); // Lambda's allow single formal specified by a single binding identifier without parentheses, special case it. if (fLambda && m_token.tk == tkID) { IdentPtr pid = m_token.GetIdentifier(this->GetHashTbl()); CreateVarDeclNode(pid, STFormal, false, nullptr, false); CheckPidIsValid(pid); this->GetScanner()->Scan(); if (m_token.tk != tkDArrow) { Error(ERRsyntax, this->GetScanner()->IchMinTok(), this->GetScanner()->IchLimTok()); } if (fLambda) { this->GetScanner()->SetYieldIsKeywordRegion(fPreviousYieldIsKeyword); this->GetScanner()->SetAwaitIsKeywordRegion(fPreviousAwaitIsKeyword); } return; } else if (fLambda && m_token.tk == tkAWAIT) { // async await => {} IdentifierExpectedError(m_token); } // Otherwise, must have a parameter list within parens. ChkCurTok(tkLParen, ERRnoLparen); // Now parse the list of arguments, if present if (m_token.tk == tkRParen) { if (fOneArg) { Error(ERRSetterMustHaveOneParameter); } } else { if (fNoArg) { Error(ERRGetterMustHaveNoParameters); } SList formals(&m_nodeAllocator); ParseNodeVar * pnodeT = nullptr; bool seenRestParameter = false; bool isNonSimpleParameterList = false; for (Js::ArgSlot argPos = 0; ; ++argPos) { bool isBindingPattern = false; if (m_scriptContext->GetConfig()->IsES6RestEnabled() && m_token.tk == tkEllipsis) { // Possible rest parameter this->GetScanner()->Scan(); seenRestParameter = true; } if (m_token.tk != tkID) { if (IsES6DestructuringEnabled() && IsPossiblePatternStart()) { // Mark that the function has a non simple parameter list before parsing the pattern since the pattern can have function definitions. this->GetCurrentFunctionNode()->SetHasNonSimpleParameterList(); this->GetCurrentFunctionNode()->SetHasDestructuredParams(); ParseNodePtr *const ppnodeVarSave = m_ppnodeVar; m_ppnodeVar = &pnodeFnc->pnodeVars; ParseNodePtr * ppNodeLex = m_currentBlockInfo->m_ppnodeLex; Assert(ppNodeLex != nullptr); ParseNodeParamPattern * paramPattern = nullptr; ParseNode * pnodePattern = nullptr; if (isTopLevelDeferredFunc) { pnodePattern = ParseDestructuredLiteral(tkLET, true /*isDecl*/, false /*topLevel*/); } else { pnodePattern = ParseDestructuredLiteral(tkLET, true /*isDecl*/, false /*topLevel*/); } // Instead of passing the STFormal all the way on many methods, it seems it is better to change the symbol type afterward. for (ParseNodePtr lexNode = *ppNodeLex; lexNode != nullptr; lexNode = lexNode->AsParseNodeVar()->pnodeNext) { Assert(lexNode->IsVarLetOrConst()); UpdateOrCheckForDuplicateInFormals(lexNode->AsParseNodeVar()->pid, &formals); lexNode->AsParseNodeVar()->sym->SetSymbolType(STFormal); if (lexNode->AsParseNodeVar()->pid == wellKnownPropertyPids.arguments) { GetCurrentFunctionNode()->grfpn |= PNodeFlags::fpnArguments_overriddenInParam; } } m_ppnodeVar = ppnodeVarSave; if (buildAST) { if (isTopLevelDeferredFunc) { Assert(pnodePattern == nullptr); // Create a dummy pattern node as we need the node to be considered for the param count paramPattern = CreateDummyParamPatternNode(this->GetScanner()->IchMinTok()); } else { Assert(pnodePattern); paramPattern = CreateParamPatternNode(pnodePattern); } // Linking the current formal parameter (which is pattern parameter) with other formals. *m_ppnodeVar = paramPattern; paramPattern->pnodeNext = nullptr; m_ppnodeVar = ¶mPattern->pnodeNext; } isBindingPattern = true; isNonSimpleParameterList = true; } else { IdentifierExpectedError(m_token); } } if (!isBindingPattern) { IdentPtr pid = m_token.GetIdentifier(this->GetHashTbl()); LPCOLESTR pNameHint = pid->Psz(); uint32 nameHintLength = pid->Cch(); uint32 nameHintOffset = 0; if (seenRestParameter) { this->GetCurrentFunctionNode()->SetHasNonSimpleParameterList(); if (flags & fFncOneArg) { // The parameter of a setter cannot be a rest parameter. Error(ERRUnexpectedEllipsis); } pnodeT = CreateDeclNode(knopVarDecl, pid, STFormal, false); pnodeT->sym->SetIsNonSimpleParameter(true); if (buildAST) { // When only validating formals, we won't have a function node. pnodeFnc->pnodeRest = pnodeT; if (!isNonSimpleParameterList) { // This is the first non-simple parameter we've seen. We need to go back // and set the Symbols of all previous parameters. MapFormalsWithoutRest(m_currentNodeFunc, [&](ParseNodePtr pnodeArg) { pnodeArg->AsParseNodeVar()->sym->SetIsNonSimpleParameter(true); }); } } isNonSimpleParameterList = true; } else { pnodeT = CreateVarDeclNode(pid, STFormal, false, nullptr, false); if (isNonSimpleParameterList) { pnodeT->sym->SetIsNonSimpleParameter(true); } } if (buildAST && pid == wellKnownPropertyPids.arguments) { // This formal parameter overrides the built-in 'arguments' object m_currentNodeFunc->grfpn |= PNodeFlags::fpnArguments_overriddenInParam; } if (fStrictFormals) { UpdateOrCheckForDuplicateInFormals(pid, &formals); } this->GetScanner()->Scan(); if (seenRestParameter && m_token.tk != tkRParen && m_token.tk != tkAsg) { Error(ERRRestLastArg); } if (m_token.tk == tkAsg && m_scriptContext->GetConfig()->IsES6DefaultArgsEnabled()) { if (seenRestParameter && m_scriptContext->GetConfig()->IsES6RestEnabled()) { Error(ERRRestWithDefault); } // In defer parse mode we have to flag the function node to indicate that it has default arguments // so that it will be considered for any syntax error scenario. // Also mark it before parsing the expression as it may contain functions. ParseNodeFnc * currentFncNode = GetCurrentFunctionNode(); if (!currentFncNode->HasDefaultArguments()) { currentFncNode->SetHasDefaultArguments(); currentFncNode->SetHasNonSimpleParameterList(); currentFncNode->firstDefaultArg = argPos; } this->GetScanner()->Scan(); ParseNodePtr pnodeInit; if (isTopLevelDeferredFunc) { // Defer default expressions if the function will be deferred, since we know they can't be evaluated // until the function is fully compiled, and generating code for a function nested inside a deferred function // creates inconsistencies. pnodeInit = ParseExpr(koplCma, nullptr, TRUE, FALSE, pNameHint, &nameHintLength, &nameHintOffset); } else { pnodeInit = ParseExpr(koplCma, nullptr, TRUE, FALSE, pNameHint, &nameHintLength, &nameHintOffset); } if (buildAST && pnodeInit && pnodeInit->nop == knopFncDecl) { Assert(nameHintLength >= nameHintOffset); ParseNodeFnc * pnodeFncInit = pnodeInit->AsParseNodeFnc(); pnodeFncInit->hint = pNameHint; pnodeFncInit->hintLength = nameHintLength; pnodeFncInit->hintOffset = nameHintOffset; } AnalysisAssert(pnodeT); pnodeT->sym->SetIsNonSimpleParameter(true); if (!isNonSimpleParameterList) { if (buildAST) { // This is the first non-simple parameter we've seen. We need to go back // and set the Symbols of all previous parameters. MapFormalsWithoutRest(m_currentNodeFunc, [&](ParseNodePtr pnodeArg) { pnodeArg->AsParseNodeVar()->sym->SetIsNonSimpleParameter(true); }); } // There may be previous parameters that need to be checked for duplicates. isNonSimpleParameterList = true; } if (buildAST) { if (!m_currentNodeFunc->HasDefaultArguments()) { CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, DefaultArgFunction, m_scriptContext); } pnodeT->pnodeInit = pnodeInit; pnodeT->ichLim = this->GetScanner()->IchLimTok(); } } } if (isNonSimpleParameterList && m_currentScope->GetHasDuplicateFormals()) { Error(ERRFormalSame); } if (flags & fFncOneArg) { if (m_token.tk != tkRParen) { Error(ERRSetterMustHaveOneParameter); } break; //enforce only one arg } if (m_token.tk != tkComma) { break; } this->GetScanner()->Scan(); if (m_token.tk == tkRParen && m_scriptContext->GetConfig()->IsES7TrailingCommaEnabled()) { break; } } if (seenRestParameter) { CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, Rest, m_scriptContext); } if (m_token.tk != tkRParen) { Error(ERRnoRparen); } if (this->GetCurrentFunctionNode()->CallsEval() || this->GetCurrentFunctionNode()->ChildCallsEval()) { Assert(pnodeFnc->HasNonSimpleParameterList()); pnodeFnc->ResetBodyAndParamScopeMerged(); } } Assert(m_token.tk == tkRParen); if (fLambda) { this->GetScanner()->SetYieldIsKeywordRegion(fPreviousYieldIsKeyword); this->GetScanner()->SetAwaitIsKeywordRegion(fPreviousAwaitIsKeyword); } } template ParseNodePtr Parser::GenerateModuleFunctionWrapper() { ParseNodePtr pnodeFnc = ParseFncDeclNoCheckScope(fFncModule, SuperRestrictionState::Disallowed, nullptr, /* needsPIDOnRCurlyScan */ false, /* fUnaryOrParen */ true); ParseNodePtr callNode = CreateCallNode(knopCall, pnodeFnc, nullptr); return callNode; } template ParseNodeFnc * Parser::GenerateEmptyConstructor(bool extends) { ParseNodeFnc * pnodeFnc; // Create the node. pnodeFnc = CreateAllowDeferNodeForOpT(); pnodeFnc->SetNested(NULL != m_currentNodeFunc); pnodeFnc->SetStrictMode(); pnodeFnc->SetDeclaration(TRUE); pnodeFnc->SetIsMethod(TRUE); pnodeFnc->SetIsClassMember(TRUE); pnodeFnc->SetIsClassConstructor(TRUE); pnodeFnc->SetIsBaseClassConstructor(!extends); pnodeFnc->SetHasNonThisStmt(); pnodeFnc->SetIsGeneratedDefault(TRUE); pnodeFnc->SetHasComputedName(); pnodeFnc->SetHasHomeObj(); pnodeFnc->SetHomeObjLocation(Js::Constants::NoRegister); pnodeFnc->ichLim = this->GetScanner()->IchLimTok(); pnodeFnc->ichMin = this->GetScanner()->IchMinTok(); pnodeFnc->cbLim = this->GetScanner()->IecpLimTok(); pnodeFnc->cbMin = this->GetScanner()->IecpMinTok(); pnodeFnc->cbStringMin = pnodeFnc->cbMin; pnodeFnc->lineNumber = this->GetScanner()->LineCur(); pnodeFnc->functionId = (*m_nextFunctionId); // In order to (re-)defer the default constructor, we need to, for instance, track // deferred class expression the way we track function expression, since we lose the part of the source // that tells us which we have. Assert(!pnodeFnc->canBeDeferred); #ifdef DBG pnodeFnc->deferredParseNextFunctionId = *(this->m_nextFunctionId); #endif AppendFunctionToScopeList(true, pnodeFnc); if (m_nextFunctionId) { (*m_nextFunctionId)++; } // Update the count of functions nested in the current parent. if (m_pnestedCount) { (*m_pnestedCount)++; } if (this->GetScanner()->IchMinTok() >= this->GetScanner()->IchMinLine()) { // In scenarios involving defer parse IchMinLine() can be incorrect for the first line after defer parse pnodeFnc->columnNumber = this->GetScanner()->IchMinTok() - this->GetScanner()->IchMinLine(); } else if (m_currentNodeFunc) { // For the first line after defer parse, compute the column relative to the column number // of the lexically parent function. ULONG offsetFromCurrentFunction = this->GetScanner()->IchMinTok() - m_currentNodeFunc->ichMin; pnodeFnc->columnNumber = m_currentNodeFunc->columnNumber + offsetFromCurrentFunction; } else { // if there is no current function, lets give a default of 0. pnodeFnc->columnNumber = 0; } int32 * pAstSizeSave = m_pCurrentAstSize; m_pCurrentAstSize = &(pnodeFnc->astSize); // Make this the current function. ParseNodeFnc * pnodeFncSave = m_currentNodeFunc; m_currentNodeFunc = pnodeFnc; ParseNodeName * argsId = nullptr; ParseNodePtr *lastNodeRef = nullptr; ParseNodeBlock * pnodeBlock = StartParseBlock(PnodeBlockType::Parameter, ScopeType_Parameter); if (buildAST && extends) { // constructor(...args) { super(...args); } // ^^^^^^^ ParseNodePtr *const ppnodeVarSave = m_ppnodeVar; m_ppnodeVar = &pnodeFnc->pnodeVars; IdentPtr pidargs = this->GetHashTbl()->PidHashNameLen(_u("args"), sizeof("args") - 1); ParseNodeVar * pnodeT = CreateVarDeclNode(pidargs, STFormal); pnodeT->sym->SetIsNonSimpleParameter(true); pnodeFnc->pnodeRest = pnodeT; PidRefStack *ref = this->PushPidRef(pidargs); argsId = CreateNameNode(pidargs, ref, pnodeFnc->ichMin, pnodeFnc->ichLim); m_ppnodeVar = ppnodeVarSave; } ParseNodeBlock * pnodeInnerBlock = StartParseBlock(PnodeBlockType::Function, ScopeType_FunctionBody); pnodeBlock->pnodeScopes = pnodeInnerBlock; pnodeFnc->pnodeBodyScope = pnodeInnerBlock; pnodeFnc->pnodeScopes = pnodeBlock; if (buildAST) { if (extends) { // constructor(...args) { super(...args); } // ^^^^^^^^^^^^^^^ Assert(argsId); ParseNodeUni * spreadArg = CreateUniNode(knopEllipsis, argsId, pnodeFnc->ichMin, pnodeFnc->ichLim); ParseNodeSpecialName * superRef = ReferenceSpecialName(wellKnownPropertyPids._superConstructor, pnodeFnc->ichMin, pnodeFnc->ichLim, true); pnodeFnc->SetHasSuperReference(TRUE); ParseNodeSuperCall * callNode = CreateSuperCallNode(superRef, spreadArg); callNode->pnodeThis = ReferenceSpecialName(wellKnownPropertyPids._this, pnodeFnc->ichMin, pnodeFnc->ichLim, true); callNode->pnodeNewTarget = ReferenceSpecialName(wellKnownPropertyPids._newTarget, pnodeFnc->ichMin, pnodeFnc->ichLim, true); callNode->spreadArgCount = 1; AddToNodeList(&pnodeFnc->pnodeBody, &lastNodeRef, callNode); } AddToNodeList(&pnodeFnc->pnodeBody, &lastNodeRef, CreateNodeForOpT()); } FinishParseBlock(pnodeInnerBlock); CreateSpecialSymbolDeclarations(pnodeFnc); FinishParseBlock(pnodeBlock); m_currentNodeFunc = pnodeFncSave; m_pCurrentAstSize = pAstSizeSave; return pnodeFnc; } template void Parser::ParseExpressionLambdaBody(ParseNodeFnc * pnodeLambda, bool fAllowIn) { ParseNodePtr *lastNodeRef = nullptr; // The lambda body is a single expression, the result of which is the return value. ParseNodeReturn * pnodeRet = nullptr; if (buildAST) { pnodeRet = CreateNodeForOpT(); pnodeRet->grfpn |= PNodeFlags::fpnSyntheticNode; pnodeLambda->pnodeScopes->pnodeStmt = pnodeRet; } IdentToken token; charcount_t lastRParen = 0; // We need to disable deferred parse mode in the scanner because the lambda body doesn't end with a right paren. // The scanner needs to create a pid in the case of a string constant token immediately following the lambda body expression. // Otherwise, we'll save null for the string constant pid which will AV during ByteCode generation. BYTE fScanDeferredFlagsSave = this->GetScanner()->SetDeferredParse(FALSE); ParseNodePtr result = ParseExpr(koplAsg, nullptr, fAllowIn, FALSE, nullptr, nullptr, nullptr, &token, false, nullptr, &lastRParen); this->GetScanner()->SetDeferredParseFlags(fScanDeferredFlagsSave); this->MarkEscapingRef(result, &token); if (buildAST) { pnodeRet->pnodeExpr = result; pnodeRet->ichMin = pnodeRet->pnodeExpr->ichMin; pnodeRet->ichLim = pnodeRet->pnodeExpr->ichLim; // Pushing a statement node with PushStmt<>() normally does this initialization // but do it here manually since we know there is no outer statement node. pnodeRet->grfnop = 0; pnodeRet->pnodeOuter = nullptr; pnodeLambda->ichLim = max(pnodeRet->ichLim, lastRParen); pnodeLambda->cbLim = this->GetScanner()->IecpLimTokPrevious(); pnodeLambda->pnodeScopes->ichLim = pnodeRet->ichLim; pnodeLambda->pnodeBody = nullptr; AddToNodeList(&pnodeLambda->pnodeBody, &lastNodeRef, pnodeRet); // Append an EndCode node. ParseNodePtr end = CreateNodeForOpT(pnodeRet->ichLim); end->ichLim = end->ichMin; // make end code zero width at the immediate end of lambda body AddToNodeList(&pnodeLambda->pnodeBody, &lastNodeRef, end); // Lambda's do not have arguments binding pnodeLambda->SetHasReferenceableBuiltInArguments(false); } else { pnodeLambda->ichLim = max(this->GetScanner()->IchLimTokPrevious(), lastRParen); pnodeLambda->cbLim = this->GetScanner()->IecpLimTokPrevious(); } } void Parser::CheckStrictFormalParameters() { if (m_token.tk == tkID) { // single parameter arrow function case IdentPtr pid = m_token.GetIdentifier(this->GetHashTbl()); CheckStrictModeEvalArgumentsUsage(pid); return; } Assert(m_token.tk == tkLParen); this->GetScanner()->ScanForcingPid(); if (m_token.tk != tkRParen) { SList formals(&m_nodeAllocator); for (;;) { if (m_token.tk != tkID) { IdentifierExpectedError(m_token); } IdentPtr pid = m_token.GetIdentifier(this->GetHashTbl()); CheckStrictModeEvalArgumentsUsage(pid); if (formals.Has(pid)) { Error(ERRES5ArgSame, this->GetScanner()->IchMinTok(), this->GetScanner()->IchLimTok()); } else { formals.Prepend(pid); } this->GetScanner()->Scan(); if (m_token.tk == tkAsg && m_scriptContext->GetConfig()->IsES6DefaultArgsEnabled()) { this->GetScanner()->Scan(); // We can avoid building the AST since we are just checking the default expression. ParseNodePtr pnodeInit = ParseExpr(koplCma); Assert(pnodeInit == nullptr); } if (m_token.tk != tkComma) { break; } this->GetScanner()->ScanForcingPid(); if (m_token.tk == tkRParen && m_scriptContext->GetConfig()->IsES7TrailingCommaEnabled()) { break; } } } Assert(m_token.tk == tkRParen); } void Parser::FinishFncNode(ParseNodeFnc * pnodeFnc, bool fAllowIn) { AnalysisAssert(pnodeFnc); // Finish the AST for a function that was deferred earlier, but which we decided // to finish after the fact. // We assume that the name(s) and arg(s) have already got parse nodes, so // we just have to do the function body. // Save the current next function Id, and resume from the old one. Js::LocalFunctionId * nextFunctionIdSave = m_nextFunctionId; Js::LocalFunctionId tempNextFunctionId = pnodeFnc->functionId + 1; this->m_nextFunctionId = &tempNextFunctionId; ParseNodeFnc * pnodeFncSave = m_currentNodeFunc; uint *pnestedCountSave = m_pnestedCount; int32* pAstSizeSave = m_pCurrentAstSize; m_currentNodeFunc = pnodeFnc; m_pCurrentAstSize = &(pnodeFnc->astSize); pnodeFnc->nestedCount = 0; m_pnestedCount = &pnodeFnc->nestedCount; bool fLambda = pnodeFnc->IsLambda(); bool fMethod = pnodeFnc->IsMethod(); // Cue up the parser to the start of the function body. if (pnodeFnc->pnodeName) { // Skip the name(s). this->GetScanner()->SetCurrentCharacter(pnodeFnc->pnodeName->ichLim, pnodeFnc->lineNumber); } else { this->GetScanner()->SetCurrentCharacter(pnodeFnc->ichMin, pnodeFnc->lineNumber); if (fMethod) { // Method. Skip identifier name, computed property name, "async", "get", "set", and '*' or '(' characters. for (;;) { this->GetScanner()->Scan(); // '[' character indicates a computed property name for this method. We should consume it. if (m_token.tk == tkLBrack) { // We don't care what the name expr is. this->GetScanner()->Scan(); ParseExpr(); Assert(m_token.tk == tkRBrack); continue; } // Quit scanning ahead when we reach a '(' character which opens the arg list. if (m_token.tk == tkLParen) { break; } } } else if (pnodeFnc->IsAccessor()) { // Getter/setter. The node text starts with the name, so eat that. this->GetScanner()->ScanNoKeywords(); } else if (!fLambda) { // Anonymous function. Skip "async", "function", and '(' or '*' characters. for (;;) { this->GetScanner()->Scan(); if (m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.async) { Assert(pnodeFnc->IsAsync()); continue; } // Quit scanning ahead when we reach a 'function' keyword which precedes the arg list. if (m_token.tk == tkFUNCTION) { break; } Assert(m_token.tk == tkLParen || m_token.tk == tkStar); } } } // switch scanner to treat 'yield' as keyword in generator functions // or as an identifier in non-generator functions bool fPreviousYieldIsKeyword = this->GetScanner()->SetYieldIsKeywordRegion(pnodeFnc && pnodeFnc->IsGenerator()); bool fPreviousAwaitIsKeyword = this->GetScanner()->SetAwaitIsKeywordRegion(pnodeFnc && pnodeFnc->IsAsync()); // Skip the arg list. if (!fMethod) { // If this is a method, we've already advanced to the '(' token. this->GetScanner()->Scan(); } if (m_token.tk == tkStar) { Assert(pnodeFnc->IsGenerator()); this->GetScanner()->ScanNoKeywords(); } if (fLambda && m_token.tk == tkID && m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.async) { Assert(pnodeFnc->IsAsync()); this->GetScanner()->ScanNoKeywords(); } Assert(m_token.tk == tkLParen || (fLambda && m_token.tk == tkID)); this->GetScanner()->ScanNoKeywords(); if (m_token.tk != tkRParen && m_token.tk != tkDArrow) { for (;;) { if (m_token.tk == tkEllipsis) { this->GetScanner()->ScanNoKeywords(); } if (m_token.tk == tkID) { this->GetScanner()->ScanNoKeywords(); if (m_token.tk == tkAsg) { // Eat the default expression this->GetScanner()->Scan(); ParseExpr(koplCma); } } else if (IsPossiblePatternStart()) { ParseDestructuredLiteralWithScopeSave(tkLET, false/*isDecl*/, false /*topLevel*/); } else { AssertMsg(false, "Unexpected identifier prefix while fast-scanning formals"); } if (m_token.tk != tkComma) { break; } this->GetScanner()->ScanNoKeywords(); if (m_token.tk == tkRParen && m_scriptContext->GetConfig()->IsES7TrailingCommaEnabled()) { break; } } } if (m_token.tk == tkRParen) { this->GetScanner()->Scan(); } if (fLambda && m_token.tk == tkDArrow) { this->GetScanner()->Scan(); } // Finish the function body. { // Note that in IE8- modes, surrounding parentheses are considered part of function body. e.g. "( function x(){} )". // We lose that context here since we start from middle of function body. So save and restore source range info. const charcount_t ichLim = pnodeFnc->ichLim; const size_t cbLim = pnodeFnc->cbLim; this->FinishFncDecl(pnodeFnc, NULL, fLambda, /* skipCurlyBraces */ false, fAllowIn); #if DBG // The pnode extent may not match the original extent. // We expect this to happen only when there are trailing ")"'s. // Consume them and make sure that's all we've got. if (pnodeFnc->ichLim != ichLim) { Assert(pnodeFnc->ichLim < ichLim); this->GetScanner()->SetCurrentCharacter(pnodeFnc->ichLim); while (this->GetScanner()->IchLimTok() != ichLim) { this->GetScanner()->ScanNoKeywords(); Assert(m_token.tk == tkRParen); } } #endif pnodeFnc->ichLim = ichLim; pnodeFnc->cbLim = cbLim; } m_currentNodeFunc = pnodeFncSave; m_pCurrentAstSize = pAstSizeSave; m_pnestedCount = pnestedCountSave; Assert(m_pnestedCount); Assert(tempNextFunctionId == pnodeFnc->deferredParseNextFunctionId); this->m_nextFunctionId = nextFunctionIdSave; this->GetScanner()->SetYieldIsKeywordRegion(fPreviousYieldIsKeyword); this->GetScanner()->SetAwaitIsKeywordRegion(fPreviousAwaitIsKeyword); } void Parser::FinishFncDecl(ParseNodeFnc * pnodeFnc, LPCOLESTR pNameHint, bool fLambda, bool skipCurlyBraces, bool fAllowIn) { LPCOLESTR name = NULL; JS_ETW(int32 startAstSize = *m_pCurrentAstSize); if (IS_JS_ETW(EventEnabledJSCRIPT_PARSE_METHOD_START()) || PHASE_TRACE1(Js::DeferParsePhase)) { name = GetFunctionName(pnodeFnc, pNameHint); m_functionBody = NULL; // for nested functions we do not want to get the name of the top deferred function return name; JS_ETW(EventWriteJSCRIPT_PARSE_METHOD_START(m_sourceContextInfo->dwHostSourceContext, GetScriptContext(), pnodeFnc->functionId, 0, m_parseType, name)); OUTPUT_TRACE(Js::DeferParsePhase, _u("Parsing function (%s) : %s (%d)\n"), GetParseType(), name, pnodeFnc->functionId); } JS_ETW_INTERNAL(EventWriteJSCRIPT_PARSE_FUNC(GetScriptContext(), pnodeFnc->functionId, /*Undefer*/FALSE)); // Do the work of creating an AST for a function body. // This is common to the un-deferred case and the case in which we un-defer late in the game. Assert(pnodeFnc->nop == knopFncDecl); if (fLambda && m_token.tk != tkLCurly) { ParseExpressionLambdaBody(pnodeFnc, fAllowIn); } else { if (!skipCurlyBraces) { ChkCurTok(tkLCurly, ERRnoLcurly); } ParseNodePtr * lastNodeRef = nullptr; ParseStmtList(&pnodeFnc->pnodeBody, &lastNodeRef, SM_OnFunctionCode, true /* isSourceElementList */); // Append an EndCode node. AddToNodeList(&pnodeFnc->pnodeBody, &lastNodeRef, CreateNodeForOpT()); if (!skipCurlyBraces) { ChkCurTokNoScan(tkRCurly, ERRnoRcurly); } pnodeFnc->ichLim = this->GetScanner()->IchLimTok(); pnodeFnc->cbLim = this->GetScanner()->IecpLimTok(); } #ifdef ENABLE_JS_ETW int32 astSize = *m_pCurrentAstSize - startAstSize; EventWriteJSCRIPT_PARSE_METHOD_STOP(m_sourceContextInfo->dwHostSourceContext, GetScriptContext(), pnodeFnc->functionId, astSize, m_parseType, name); #endif } ParseNodeVar * Parser::CreateSpecialVarDeclNode(ParseNodeFnc * pnodeFnc, IdentPtr pid) { ParseNodeVar * pnode = InsertVarAtBeginning(pnodeFnc, pid); pnode->grfpn |= fpnSpecialSymbol; // special symbol must not be global pnode->sym->SetIsGlobal(false); return pnode; } ParseNodeVar * Parser::InsertVarAtBeginning(ParseNodeFnc * pnodeFnc, IdentPtr pid) { ParseNodeVar * pnode = nullptr; if (m_ppnodeVar == &pnodeFnc->pnodeVars) { pnode = CreateVarDeclNode(pid, STVariable, true, pnodeFnc); } else { ParseNodePtr * const ppnodeVarSave = m_ppnodeVar; m_ppnodeVar = &pnodeFnc->pnodeVars; pnode = CreateVarDeclNode(pid, STVariable, true, pnodeFnc); m_ppnodeVar = ppnodeVarSave; } Assert(pnode); return pnode; } ParseNodeVar * Parser::AddArgumentsNodeToVars(ParseNodeFnc * pnodeFnc) { Assert(!GetCurrentFunctionNode()->IsLambda()); ParseNodeVar * argNode = InsertVarAtBeginning(pnodeFnc, wellKnownPropertyPids.arguments); argNode->grfpn |= PNodeFlags::fpnArguments; // Flag this as the built-in arguments node return argNode; } void Parser::UpdateArgumentsNode(ParseNodeFnc * pnodeFnc, ParseNodeVar * argNode) { if ((pnodeFnc->grfpn & PNodeFlags::fpnArguments_overriddenInParam) || pnodeFnc->IsLambda()) { // There is a parameter named arguments. So we don't have to create the built-in arguments. pnodeFnc->SetHasReferenceableBuiltInArguments(false); } else if ((pnodeFnc->grfpn & PNodeFlags::fpnArguments_overriddenByDecl) && pnodeFnc->IsBodyAndParamScopeMerged()) { // In non-split scope case there is a var or function definition named arguments in the body pnodeFnc->SetHasReferenceableBuiltInArguments(false); } else { pnodeFnc->SetHasReferenceableBuiltInArguments(true); Assert(argNode); } if (argNode != nullptr && !argNode->sym->IsArguments()) { // A duplicate definition has updated the declaration node. Need to reset it back. argNode->grfpn |= PNodeFlags::fpnArguments; argNode->sym->SetDecl(argNode); } } LPCOLESTR Parser::GetFunctionName(ParseNodeFnc * pnodeFnc, LPCOLESTR pNameHint) { LPCOLESTR name = nullptr; if (pnodeFnc->pnodeName != nullptr) { Assert(pnodeFnc->pnodeName->nop == knopVarDecl); name = pnodeFnc->pnodeName->pid->Psz(); } if (name == nullptr && pNameHint != nullptr) { name = pNameHint; } if (name == nullptr && (pnodeFnc->IsLambda() || (!pnodeFnc->IsDeclaration() && !pnodeFnc->IsMethod()))) { name = Js::Constants::AnonymousFunction; } if (name == nullptr && m_functionBody != nullptr) { name = m_functionBody->GetExternalDisplayName(); } else if (name == nullptr) { name = Js::Constants::AnonymousFunction; } return name; } IdentPtr Parser::ParseClassPropertyName(IdentPtr * pidHint) { if (m_token.tk == tkID || m_token.tk == tkStrCon || m_token.IsReservedWord()) { IdentPtr pid; if (m_token.tk == tkStrCon) { if (this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) { Error(ERRES5NoOctal); } pid = m_token.GetStr(); } else { pid = m_token.GetIdentifier(this->GetHashTbl()); } *pidHint = pid; return pid; } else if (m_token.tk == tkIntCon) { if (this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) { Error(ERRES5NoOctal); } return this->GetScanner()->PidFromLong(m_token.GetLong()); } else if (m_token.tk == tkFltCon) { if (this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) { Error(ERRES5NoOctal); } return this->GetScanner()->PidFromDbl(m_token.GetDouble()); } Error(ERRnoMemberIdent); } LPCOLESTR Parser::ConstructFinalHintNode(IdentPtr pClassName, IdentPtr pMemberName, IdentPtr pGetSet, bool isStatic, uint32* nameLength, uint32* pShortNameOffset, bool isComputedName, LPCOLESTR pMemberNameHint) { if ((pMemberName == nullptr && !isComputedName) || (pMemberNameHint == nullptr && isComputedName) || !CONFIG_FLAG(UseFullName)) { return nullptr; } LPCOLESTR pFinalName = isComputedName ? pMemberNameHint : pMemberName->Psz(); uint32 fullNameHintLength = (uint32)wcslen(pFinalName); uint32 shortNameOffset = 0; if (!isStatic) { // Add prototype. pFinalName = AppendNameHints(wellKnownPropertyPids.prototype, pFinalName, &fullNameHintLength, &shortNameOffset); } if (pClassName) { uint32 classNameOffset = 0; pFinalName = AppendNameHints(pClassName, pFinalName, &fullNameHintLength, &classNameOffset); shortNameOffset += classNameOffset; } if (pGetSet) { // displays as get/set prototype.funcname uint32 getSetOffset = 0; pFinalName = AppendNameHints(pGetSet, pFinalName, &fullNameHintLength, &getSetOffset, true); shortNameOffset += getSetOffset; } *nameLength = fullNameHintLength; *pShortNameOffset = shortNameOffset; return pFinalName; } template ParseNodeClass * Parser::ParseClassDecl(BOOL isDeclaration, LPCOLESTR pNameHint, uint32 *pHintLength, uint32 *pShortNameOffset) { bool hasConstructor = false; bool hasExtends = false; IdentPtr name = nullptr; ParseNodeVar * pnodeName = nullptr; ParseNodeFnc * pnodeConstructor = nullptr; ParseNodePtr pnodeExtends = nullptr; ParseNodePtr pnodeMembers = nullptr; ParseNodePtr *lastMemberNodeRef = nullptr; ParseNodePtr pnodeStaticMembers = nullptr; ParseNodePtr *lastStaticMemberNodeRef = nullptr; uint32 nameHintLength = pHintLength ? *pHintLength : 0; uint32 nameHintOffset = pShortNameOffset ? *pShortNameOffset : 0; ArenaAllocator tempAllocator(_u("ClassMemberNames"), m_nodeAllocator.GetPageAllocator(), Parser::OutOfMemory); size_t cbMinConstructor = 0; ParseNodeClass * pnodeClass = nullptr; if (buildAST) { pnodeClass = CreateNodeForOpT(); CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, Class, m_scriptContext); cbMinConstructor = this->GetScanner()->IecpMinTok(); } this->GetScanner()->Scan(); if (m_token.tk == tkID) { name = m_token.GetIdentifier(this->GetHashTbl()); this->GetScanner()->Scan(); } else if (isDeclaration) { IdentifierExpectedError(m_token); } if (isDeclaration && name == wellKnownPropertyPids.arguments && GetCurrentBlockInfo()->pnodeBlock->blockType == Function) { GetCurrentFunctionNode()->grfpn |= PNodeFlags::fpnArguments_overriddenByDecl; } BOOL strictSave = m_fUseStrictMode; m_fUseStrictMode = TRUE; ParseNodeVar * pnodeDeclName = nullptr; if (isDeclaration) { pnodeDeclName = CreateBlockScopedDeclNode(name, knopLetDecl); } ParseNodePtr *ppnodeScopeSave = nullptr; ParseNodePtr *ppnodeExprScopeSave = nullptr; ParseNodeBlock * pnodeBlock = StartParseBlock(PnodeBlockType::Regular, ScopeType_Block); if (buildAST) { PushFuncBlockScope(pnodeBlock, &ppnodeScopeSave, &ppnodeExprScopeSave); pnodeClass->pnodeBlock = pnodeBlock; } if (name) { pnodeName = CreateBlockScopedDeclNode(name, knopConstDecl); } if (m_token.tk == tkEXTENDS) { this->GetScanner()->Scan(); pnodeExtends = ParseTerm(); hasExtends = true; } if (m_token.tk != tkLCurly) { Error(ERRnoLcurly); } OUTPUT_TRACE_DEBUGONLY(Js::ES6VerboseFlag, _u("Parsing class (%s) : %s\n"), GetParseType(), name ? name->Psz() : _u("anonymous class")); RestorePoint beginClass; this->GetScanner()->Capture(&beginClass); this->GetScanner()->ScanForcingPid(); IdentPtr pClassNamePid = pnodeName ? pnodeName->pid : nullptr; for (;;) { if (m_token.tk == tkSColon) { this->GetScanner()->ScanForcingPid(); continue; } if (m_token.tk == tkRCurly) { break; } bool isStatic = false; if (m_token.tk == tkSTATIC) { // 'static' can be used as an IdentifierName here, even in strict mode code. We need to see the next token before we know // if this is being used as a keyword. This is similar to the way we treat 'let' in some cases. // See https://tc39.github.io/ecma262/#sec-keywords for more info. RestorePoint beginStatic; this->GetScanner()->Capture(&beginStatic); this->GetScanner()->ScanForcingPid(); if (m_token.tk == tkLParen) { this->GetScanner()->SeekTo(beginStatic); } else { isStatic = true; } } ushort fncDeclFlags = fFncNoName | fFncMethod | fFncClassMember; charcount_t ichMin = this->GetScanner()->IchMinTok(); size_t iecpMin = this->GetScanner()->IecpMinTok(); ParseNodePtr pnodeMemberName = nullptr; IdentPtr pidHint = nullptr; IdentPtr memberPid = nullptr; LPCOLESTR pMemberNameHint = nullptr; uint32 memberNameHintLength = 0; uint32 memberNameOffset = 0; bool isComputedName = false; bool isAsyncMethod = false; if (m_token.tk == tkID && m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.async && m_scriptContext->GetConfig()->IsES7AsyncAndAwaitEnabled()) { RestorePoint parsedAsync; this->GetScanner()->Capture(&parsedAsync); ichMin = this->GetScanner()->IchMinTok(); iecpMin = this->GetScanner()->IecpMinTok(); this->GetScanner()->Scan(); if (m_token.tk == tkLParen || this->GetScanner()->FHadNewLine()) { this->GetScanner()->SeekTo(parsedAsync); } else { isAsyncMethod = true; } } bool isGenerator = m_scriptContext->GetConfig()->IsES6GeneratorsEnabled() && m_token.tk == tkStar; if (isGenerator) { fncDeclFlags |= fFncGenerator; this->GetScanner()->ScanForcingPid(); } if (m_token.tk == tkLBrack && m_scriptContext->GetConfig()->IsES6ObjectLiteralsEnabled()) { // Computed member name: [expr] () { } LPCOLESTR emptyHint = nullptr; ParseComputedName(&pnodeMemberName, &emptyHint, &pMemberNameHint, &memberNameHintLength, &memberNameOffset); isComputedName = true; } else // not computed name { memberPid = this->ParseClassPropertyName(&pidHint); if (pidHint) { pMemberNameHint = pidHint->Psz(); memberNameHintLength = pidHint->Cch(); } } if (buildAST && memberPid) { pnodeMemberName = CreateStrNode(memberPid); } if (!isStatic && memberPid == wellKnownPropertyPids.constructor) { if (hasConstructor || isAsyncMethod) { Error(ERRsyntax); } hasConstructor = true; LPCOLESTR pConstructorName = nullptr; uint32 constructorNameLength = 0; uint32 constructorShortNameHintOffset = 0; if (pnodeName && pnodeName->pid) { pConstructorName = pnodeName->pid->Psz(); constructorNameLength = pnodeName->pid->Cch(); } else { pConstructorName = pNameHint; constructorNameLength = nameHintLength; constructorShortNameHintOffset = nameHintOffset; } { SuperRestrictionState::State state = hasExtends ? SuperRestrictionState::CallAndPropertyAllowed : SuperRestrictionState::PropertyAllowed; // Add the class constructor flag and base class constructor flag if pnodeExtends is nullptr fncDeclFlags |= fFncClassConstructor | (hasExtends ? kFunctionNone : fFncBaseClassConstructor); pnodeConstructor = ParseFncDeclNoCheckScope(fncDeclFlags, state, pConstructorName, /* needsPIDOnRCurlyScan */ true); } if (pnodeConstructor->IsGenerator()) { Error(ERRConstructorCannotBeGenerator); } Assert(constructorNameLength >= constructorShortNameHintOffset); // The constructor function will get the same name as class. pnodeConstructor->hint = pConstructorName; pnodeConstructor->hintLength = constructorNameLength; pnodeConstructor->hintOffset = constructorShortNameHintOffset; pnodeConstructor->pid = pnodeName && pnodeName->pid ? pnodeName->pid : wellKnownPropertyPids.constructor; pnodeConstructor->SetHasNonThisStmt(); pnodeConstructor->SetHasComputedName(); pnodeConstructor->SetHasHomeObj(); } else { ParseNodePtr pnodeMember = nullptr; bool isMemberNamedGetOrSet = false; RestorePoint beginMethodName; this->GetScanner()->Capture(&beginMethodName); if (memberPid == wellKnownPropertyPids.get || memberPid == wellKnownPropertyPids.set) { this->GetScanner()->ScanForcingPid(); } if (m_token.tk == tkLParen) { this->GetScanner()->SeekTo(beginMethodName); isMemberNamedGetOrSet = true; } if ((memberPid == wellKnownPropertyPids.get || memberPid == wellKnownPropertyPids.set) && !isMemberNamedGetOrSet) { bool isGetter = (memberPid == wellKnownPropertyPids.get); if (m_token.tk == tkLBrack && m_scriptContext->GetConfig()->IsES6ObjectLiteralsEnabled()) { // Computed get/set member name: get|set [expr] () { } LPCOLESTR emptyHint = nullptr; ParseComputedName(&pnodeMemberName, &emptyHint, &pMemberNameHint, &memberNameHintLength, &memberNameOffset); isComputedName = true; } else // not computed name { memberPid = this->ParseClassPropertyName(&pidHint); } if ((isStatic ? (memberPid == wellKnownPropertyPids.prototype) : (memberPid == wellKnownPropertyPids.constructor)) || isAsyncMethod) { Error(ERRsyntax); } if (buildAST && memberPid && !isComputedName) { pnodeMemberName = CreateStrNode(memberPid); } ParseNodeFnc * pnodeFnc = nullptr; { pnodeFnc = ParseFncDeclNoCheckScope(fncDeclFlags | (isGetter ? fFncNoArg : fFncOneArg), SuperRestrictionState::PropertyAllowed, pidHint ? pidHint->Psz() : nullptr, /* needsPIDOnRCurlyScan */ true); } pnodeFnc->SetIsStaticMember(isStatic); if (isComputedName) { pnodeFnc->SetHasComputedName(); } pnodeFnc->SetHasHomeObj(); if (buildAST) { pnodeFnc->SetIsAccessor(); pnodeMember = CreateBinNode(isGetter ? knopGetMember : knopSetMember, pnodeMemberName, pnodeFnc); pMemberNameHint = ConstructFinalHintNode(pClassNamePid, pidHint, isGetter ? wellKnownPropertyPids.get : wellKnownPropertyPids.set, isStatic, &memberNameHintLength, &memberNameOffset, isComputedName, pMemberNameHint); } } else { if (isStatic && (memberPid == wellKnownPropertyPids.prototype)) { Error(ERRsyntax); } ParseNodeFnc * pnodeFnc = nullptr; { if (isAsyncMethod) { fncDeclFlags |= fFncAsync; } pnodeFnc = ParseFncDeclNoCheckScope(fncDeclFlags, SuperRestrictionState::PropertyAllowed, pidHint ? pidHint->Psz() : nullptr, /* needsPIDOnRCurlyScan */ true); if (isAsyncMethod) { pnodeFnc->cbMin = iecpMin; pnodeFnc->ichMin = ichMin; } if (isAsyncMethod || isGenerator || isComputedName) { pnodeFnc->cbStringMin = iecpMin; } } pnodeFnc->SetIsStaticMember(isStatic); if (isComputedName) { pnodeFnc->SetHasComputedName(); } pnodeFnc->SetHasHomeObj(); if (buildAST) { pnodeMember = CreateBinNode(knopMember, pnodeMemberName, pnodeFnc); pMemberNameHint = ConstructFinalHintNode(pClassNamePid, pidHint, nullptr /*pgetset*/, isStatic, &memberNameHintLength, &memberNameOffset, isComputedName, pMemberNameHint); } } if (buildAST) { Assert(memberNameHintLength >= memberNameOffset); pnodeMember->AsParseNodeBin()->pnode2->AsParseNodeFnc()->hint = pMemberNameHint; // Fully qualified name pnodeMember->AsParseNodeBin()->pnode2->AsParseNodeFnc()->hintLength = memberNameHintLength; pnodeMember->AsParseNodeBin()->pnode2->AsParseNodeFnc()->hintOffset = memberNameOffset; pnodeMember->AsParseNodeBin()->pnode2->AsParseNodeFnc()->pid = memberPid; // Short name AddToNodeList(isStatic ? &pnodeStaticMembers : &pnodeMembers, isStatic ? &lastStaticMemberNodeRef : &lastMemberNodeRef, pnodeMember); } } } size_t cbLimConstructor = 0; if (buildAST) { pnodeClass->ichLim = this->GetScanner()->IchLimTok(); cbLimConstructor = this->GetScanner()->IecpLimTok(); } if (!hasConstructor) { OUTPUT_TRACE_DEBUGONLY(Js::ES6VerboseFlag, _u("Generating constructor (%s) : %s\n"), GetParseType(), name ? name->Psz() : _u("anonymous class")); RestorePoint endClass; this->GetScanner()->Capture(&endClass); this->GetScanner()->SeekTo(beginClass); pnodeConstructor = GenerateEmptyConstructor(pnodeExtends != nullptr); if (buildAST) { if (pClassNamePid) { pnodeConstructor->hint = pClassNamePid->Psz(); pnodeConstructor->hintLength = pClassNamePid->Cch(); pnodeConstructor->hintOffset = 0; } else { Assert(nameHintLength >= nameHintOffset); pnodeConstructor->hint = pNameHint; pnodeConstructor->hintLength = nameHintLength; pnodeConstructor->hintOffset = nameHintOffset; } pnodeConstructor->pid = pClassNamePid; } this->GetScanner()->SeekTo(endClass); } if (buildAST) { pnodeConstructor->cbMin = cbMinConstructor; pnodeConstructor->cbStringMin = cbMinConstructor; pnodeConstructor->cbLim = cbLimConstructor; pnodeConstructor->ichMin = pnodeClass->ichMin; pnodeConstructor->ichLim = pnodeClass->ichLim; PopFuncBlockScope(ppnodeScopeSave, ppnodeExprScopeSave); pnodeClass->pnodeDeclName = pnodeDeclName; pnodeClass->pnodeName = pnodeName; pnodeClass->pnodeConstructor = pnodeConstructor; pnodeClass->pnodeExtends = pnodeExtends; pnodeClass->pnodeMembers = pnodeMembers; pnodeClass->pnodeStaticMembers = pnodeStaticMembers; pnodeClass->isDefaultModuleExport = false; } FinishParseBlock(pnodeBlock); m_fUseStrictMode = strictSave; this->GetScanner()->Scan(); return pnodeClass; } template ParseNodePtr Parser::ParseStringTemplateDecl(ParseNodePtr pnodeTagFnc) { ParseNodePtr pnodeStringLiterals = nullptr; ParseNodePtr* lastStringLiteralNodeRef = nullptr; ParseNodePtr pnodeRawStringLiterals = nullptr; ParseNodePtr* lastRawStringLiteralNodeRef = nullptr; ParseNodePtr pnodeSubstitutionExpressions = nullptr; ParseNodePtr* lastSubstitutionExpressionNodeRef = nullptr; ParseNodePtr pnodeTagFncArgs = nullptr; ParseNodePtr* lastTagFncArgNodeRef = nullptr; ParseNodeStr * stringLiteral = nullptr; ParseNodeStr * stringLiteralRaw = nullptr; ParseNodeStrTemplate * pnodeStringTemplate = nullptr; ParseNode * pnodeReturn = nullptr; bool templateClosed = false; const bool isTagged = pnodeTagFnc != nullptr; uint16 stringConstantCount = 0; charcount_t ichMin = 0; Assert(m_token.tk == tkStrTmplBasic || m_token.tk == tkStrTmplBegin); if (buildAST) { pnodeReturn = pnodeStringTemplate = CreateNodeForOpT(); pnodeStringTemplate->countStringLiterals = 0; pnodeStringTemplate->isTaggedTemplate = isTagged ? TRUE : FALSE; // If this is a tagged string template, we need to start building the arg list for the call if (isTagged) { ichMin = pnodeTagFnc->ichMin; AddToNodeListEscapedUse(&pnodeTagFncArgs, &lastTagFncArgNodeRef, pnodeStringTemplate); } } CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, StringTemplates, m_scriptContext); OUTPUT_TRACE_DEBUGONLY( Js::StringTemplateParsePhase, _u("Starting to parse a string template (%s)...\n\tis tagged = %s\n"), GetParseType(), isTagged ? _u("true") : _u("false (Raw and cooked strings will not differ!)")); // String template grammar // `...` Simple string template // `...${ String template beginning // }...${ String template middle // }...` String template end while (!templateClosed) { // First, extract the string constant part - we always have one if (IsStrictMode() && this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber()) { Error(ERRES5NoOctal); } // We are not able to pass more than a ushort worth of arguments to the tag // so use that as a logical limit on the number of string constant pieces. if (stringConstantCount >= Js::Constants::MaxAllowedArgs) { Error(ERRTooManyArgs); } // Keep track of the string literal count (must be the same for raw strings) // We use this in code gen so we don't need to count the string literals list stringConstantCount++; // If we are not creating parse nodes, there is no need to create strings if (buildAST) { stringLiteral = CreateStrNode(m_token.GetStr()); AddToNodeList(&pnodeStringLiterals, &lastStringLiteralNodeRef, stringLiteral); // We only need to collect a raw string when we are going to pass the string template to a tag if (isTagged) { // Make the scanner create a PID for the raw string constant for the preceding scan IdentPtr pid = this->GetScanner()->GetSecondaryBufferAsPid(); stringLiteralRaw = CreateStrNode(pid); // Should have gotten a raw string literal above AddToNodeList(&pnodeRawStringLiterals, &lastRawStringLiteralNodeRef, stringLiteralRaw); } else { #if DBG // Assign the raw string for debug tracing below stringLiteralRaw = stringLiteral; #endif } OUTPUT_TRACE_DEBUGONLY( Js::StringTemplateParsePhase, _u("Parsed string constant: \n\tcooked = \"%s\" \n\traw = \"%s\" \n\tdiffer = %d\n"), stringLiteral->pid->Psz(), stringLiteralRaw->pid->Psz(), stringLiteral->pid->Psz() == stringLiteralRaw->pid->Psz() ? 0 : 1); } switch (m_token.tk) { case tkStrTmplEnd: case tkStrTmplBasic: // We do not need to parse an expression for either the end or basic string template tokens templateClosed = true; break; case tkStrTmplBegin: case tkStrTmplMid: { // In the middle or begin string template token case, we need to parse an expression next this->GetScanner()->Scan(); // Parse the contents of the curly braces as an expression ParseNodePtr expression = ParseExpr(0); // After parsing expression, scan should leave us with an RCurly token. // Use the NoScan version so we do not automatically perform a scan - we need to // set the scan state before next scan but we don't want to set that state if // the token is not as expected since we'll error in that case. ChkCurTokNoScan(tkRCurly, ERRnoRcurly); // Notify the scanner that it should scan for a middle or end string template token this->GetScanner()->SetScanState(Scanner_t::ScanState::ScanStateStringTemplateMiddleOrEnd); this->GetScanner()->Scan(); if (buildAST) { // If we are going to call the tag function, add this expression into the list of args if (isTagged) { AddToNodeListEscapedUse(&pnodeTagFncArgs, &lastTagFncArgNodeRef, expression); } else { // Otherwise add it to the substitution expression list // TODO: Store the arguments and substitution expressions in a single list? AddToNodeList(&pnodeSubstitutionExpressions, &lastSubstitutionExpressionNodeRef, expression); } } if (!(m_token.tk == tkStrTmplMid || m_token.tk == tkStrTmplEnd)) { // Scan with ScanState ScanStateStringTemplateMiddleOrEnd should only return // tkStrTmpMid/End unless it is EOF or tkScanError Assert(m_token.tk == tkEOF || m_token.tk == tkScanError); Error(ERRsyntax); } OUTPUT_TRACE_DEBUGONLY(Js::StringTemplateParsePhase, _u("Parsed expression\n")); } break; default: Assert(false); break; } } if (buildAST) { pnodeStringTemplate->pnodeStringLiterals = pnodeStringLiterals; pnodeStringTemplate->pnodeStringRawLiterals = pnodeRawStringLiterals; pnodeStringTemplate->pnodeSubstitutionExpressions = pnodeSubstitutionExpressions; pnodeStringTemplate->countStringLiterals = stringConstantCount; // We should still have the last string literal. // Use the char offset of the end of that constant as the end of the string template. pnodeStringTemplate->ichLim = stringLiteral->ichLim; // If this is a tagged template, we now have the argument list and can construct a call node if (isTagged) { // Return the call node here and let the byte code generator Emit the string template automagically ParseNodeCall * pnodeCall; pnodeReturn = pnodeCall = CreateCallNode(knopCall, pnodeTagFnc, pnodeTagFncArgs, ichMin, pnodeStringTemplate->ichLim); // We need to set the arg count explicitly pnodeCall->argCount = stringConstantCount; pnodeCall->hasDestructuring = m_hasDestructuringPattern; } } this->GetScanner()->Scan(); return pnodeReturn; } LPCOLESTR Parser::FormatPropertyString(LPCOLESTR propertyString, ParseNodePtr pNode, uint32 *fullNameHintLength, uint32 *pShortNameOffset) { // propertyString could be null, such as 'this.foo' = // propertyString could be empty, found in pattern as in (-1)[""][(x = z)] OpCode op = pNode->nop; LPCOLESTR rightNode = nullptr; if (propertyString == nullptr) { propertyString = _u(""); } if (op != knopInt && op != knopFlt && op != knopName && op != knopStr) { rightNode = _u(""); } else if (op == knopStr) { return AppendNameHints(propertyString, pNode->AsParseNodeStr()->pid, fullNameHintLength, pShortNameOffset, false, true/*add brackets*/); } else if (op == knopFlt) { rightNode = this->GetScanner()->StringFromDbl(pNode->AsParseNodeFloat()->dbl); } else { rightNode = op == knopInt ? this->GetScanner()->StringFromLong(pNode->AsParseNodeInt()->lw) : pNode->AsParseNodeName()->pid->Psz(); } return AppendNameHints(propertyString, rightNode, fullNameHintLength, pShortNameOffset, false, true/*add brackets*/); } LPCOLESTR Parser::ConstructNameHint(ParseNodeBin * pNode, uint32* fullNameHintLength, uint32 *pShortNameOffset) { Assert(pNode != nullptr); Assert(pNode->nop == knopDot || pNode->nop == knopIndex); // This method recursively visits nodes in the AST and could cause an SOE crash for long knopDot chains. // Although this method could be made non-recursive, Emit (ByteCodeEmitter.cpp) hits a stack probe // for shorter chains than which cause SOE here, so add a stack probe to throw SOE rather than crash on SOE. // Because of that correspondence, use Js::Constants::MinStackByteCodeVisitor (which is used in Emit) // for the stack probe here. See OS#14711878. PROBE_STACK_NO_DISPOSE(this->m_scriptContext, Js::Constants::MinStackByteCodeVisitor); LPCOLESTR leftNode = nullptr; if (pNode->pnode1->nop == knopDot || pNode->pnode1->nop == knopIndex) { leftNode = ConstructNameHint(pNode->pnode1->AsParseNodeBin(), fullNameHintLength, pShortNameOffset); } else if (pNode->pnode1->nop == knopName && !pNode->pnode1->AsParseNodeName()->IsSpecialName()) { // We need to skip special names like 'this' because those shouldn't be appended to the // name hint in the debugger stack trace. // function ctor() { // this.func = function() { // // If we break here, the stack should say we are in 'func' and not 'this.func' // } // } IdentPtr pid = pNode->pnode1->AsParseNodeName()->pid; leftNode = pid->Psz(); *fullNameHintLength = pid->Cch(); *pShortNameOffset = 0; } if (pNode->nop == knopIndex) { return FormatPropertyString( leftNode ? leftNode : Js::Constants::AnonymousFunction, // e.g. f()[0] = function () {} pNode->pnode2, fullNameHintLength, pShortNameOffset); } Assert(pNode->pnode2->nop == knopDot || pNode->pnode2->nop == knopName); LPCOLESTR rightNode = nullptr; bool wrapWithBrackets = false; if (pNode->pnode2->nop == knopDot) { rightNode = ConstructNameHint(pNode->pnode2->AsParseNodeBin(), fullNameHintLength, pShortNameOffset); } else { rightNode = pNode->pnode2->AsParseNodeName()->pid->Psz(); wrapWithBrackets = PNodeFlags::fpnIndexOperator == (pNode->grfpn & PNodeFlags::fpnIndexOperator); } Assert(rightNode != nullptr); return AppendNameHints(leftNode, rightNode, fullNameHintLength, pShortNameOffset, false, wrapWithBrackets); } LPCOLESTR Parser::AppendNameHints(LPCOLESTR leftStr, uint32 leftLen, LPCOLESTR rightStr, uint32 rightLen, uint32 *pNameLength, uint32 *pShortNameOffset, bool ignoreAddDotWithSpace, bool wrapInBrackets) { Assert(rightStr != nullptr); Assert(leftLen != 0 || wrapInBrackets); Assert(rightLen != 0 || wrapInBrackets); bool ignoreDot = rightStr[0] == _u('[') && !wrapInBrackets;//if we wrap in brackets it can be a string literal which can have brackets at the first char uint32 totalLength = leftLen + rightLen + ((ignoreDot) ? 1 : 2); // 1 (for dot or [) + 1 (for null termination) if (wrapInBrackets) { totalLength++; //1 for ']'; } WCHAR * finalName = AllocateStringOfLength(totalLength); if (leftStr != nullptr && leftLen != 0) { wcscpy_s(finalName, leftLen + 1, leftStr); } if (ignoreAddDotWithSpace) { finalName[leftLen++] = (OLECHAR)_u(' '); } // mutually exclusive from ignoreAddDotWithSpace which is used for getters/setters else if (wrapInBrackets) { finalName[leftLen++] = (OLECHAR)_u('['); finalName[totalLength - 2] = (OLECHAR)_u(']'); } else if (!ignoreDot) { finalName[leftLen++] = (OLECHAR)_u('.'); } //ignore case falls through js_wmemcpy_s(finalName + leftLen, rightLen, rightStr, rightLen); finalName[totalLength - 1] = (OLECHAR)_u('\0'); if (pNameLength != nullptr) { *pNameLength = totalLength - 1; } if (pShortNameOffset != nullptr) { *pShortNameOffset = leftLen; } return finalName; } WCHAR * Parser::AllocateStringOfLength(ULONG length) { Assert(length > 0); ULONG totalBytes; if (ULongMult(length, sizeof(OLECHAR), &totalBytes) != S_OK) { Error(ERRnoMemory); } WCHAR* finalName = (WCHAR*)this->GetHashTbl()->GetAllocator()->Alloc(totalBytes); if (finalName == nullptr) { Error(ERRnoMemory); } return finalName; } LPCOLESTR Parser::AppendNameHints(IdentPtr left, IdentPtr right, uint32 *pNameLength, uint32 *pShortNameOffset, bool ignoreAddDotWithSpace, bool wrapInBrackets) { if (pShortNameOffset != nullptr) { *pShortNameOffset = 0; } if (left == nullptr && !wrapInBrackets) { if (right) { *pNameLength = right->Cch(); return right->Psz(); } return nullptr; } uint32 leftLen = 0; LPCOLESTR leftStr = _u(""); if (left != nullptr) // if wrapInBrackets is true { leftStr = left->Psz(); leftLen = left->Cch(); } if (right == nullptr) { *pNameLength = leftLen; return left->Psz(); } uint32 rightLen = right->Cch(); return AppendNameHints(leftStr, leftLen, right->Psz(), rightLen, pNameLength, pShortNameOffset, ignoreAddDotWithSpace, wrapInBrackets); } LPCOLESTR Parser::AppendNameHints(IdentPtr left, LPCOLESTR right, uint32 *pNameLength, uint32 *pShortNameOffset, bool ignoreAddDotWithSpace, bool wrapInBrackets) { uint32 rightLen = (right == nullptr) ? 0 : (uint32)wcslen(right); if (pShortNameOffset != nullptr) { *pShortNameOffset = 0; } Assert(rightLen <= ULONG_MAX); // name hints should not exceed ULONG_MAX characters if (left == nullptr && !wrapInBrackets) { *pNameLength = rightLen; return right; } LPCOLESTR leftStr = _u(""); uint32 leftLen = 0; if (left != nullptr) // if wrapInBrackets is true { leftStr = left->Psz(); leftLen = left->Cch(); } if (rightLen == 0 && !wrapInBrackets) { *pNameLength = leftLen; return left->Psz(); } return AppendNameHints(leftStr, leftLen, right, rightLen, pNameLength, pShortNameOffset, ignoreAddDotWithSpace, wrapInBrackets); } LPCOLESTR Parser::AppendNameHints(LPCOLESTR left, IdentPtr right, uint32 *pNameLength, uint32 *pShortNameOffset, bool ignoreAddDotWithSpace, bool wrapInBrackets) { uint32 leftLen = (left == nullptr) ? 0 : (uint32)wcslen(left); if (pShortNameOffset != nullptr) { *pShortNameOffset = 0; } Assert(leftLen <= ULONG_MAX); // name hints should not exceed ULONG_MAX characters if (left == nullptr || (leftLen == 0 && !wrapInBrackets)) { if (right != nullptr) { *pNameLength = right->Cch(); return right->Psz(); } return nullptr; } if (right == nullptr) { *pNameLength = leftLen; return left; } uint32 rightLen = right->Cch(); return AppendNameHints(left, leftLen, right->Psz(), rightLen, pNameLength, pShortNameOffset, ignoreAddDotWithSpace, wrapInBrackets); } LPCOLESTR Parser::AppendNameHints(LPCOLESTR left, LPCOLESTR right, uint32 *pNameLength, uint32 *pShortNameOffset, bool ignoreAddDotWithSpace, bool wrapInBrackets) { uint32 leftLen = (left == nullptr) ? 0 : (uint32)wcslen(left); uint32 rightLen = (right == nullptr) ? 0 : (uint32)wcslen(right); if (pShortNameOffset != nullptr) { *pShortNameOffset = 0; } Assert(rightLen <= ULONG_MAX && leftLen <= ULONG_MAX); // name hints should not exceed ULONG_MAX characters if (leftLen == 0 && !wrapInBrackets) { *pNameLength = right ? rightLen : 0; return right; } if (rightLen == 0 && !wrapInBrackets) { *pNameLength = leftLen; return left; } return AppendNameHints(left, leftLen, right, rightLen, pNameLength, pShortNameOffset, ignoreAddDotWithSpace, wrapInBrackets); } /** * Emits a spread error if there is no ambiguity, or marks defers the error for * when we can determine if it is a rest error or a spread error. * * The ambiguity arises when we are parsing a lambda parameter list but we have * not seen the => token. At this point, we are either in a parenthesized * expression or a parameter list, and cannot issue an error until the matching * RParen has been scanned. * * The actual emission of the error happens in ParseExpr, when we first know if * the expression is a lambda parameter list or not. * */ void Parser::DeferOrEmitPotentialSpreadError(ParseNodePtr pnodeT) { if (m_funcParenExprDepth > 0) { if (m_token.tk == tkRParen) { if (!m_deferEllipsisError) { // Capture only the first error instance. Because a lambda will cause a reparse in a formals context, we can assume // that this will be a spread error. Nested paren exprs will have their own error instance. this->GetScanner()->Capture(&m_deferEllipsisErrorLoc); m_deferEllipsisError = true; } } else { Error(ERRUnexpectedEllipsis); } } else { Error(ERRInvalidSpreadUse); } } bool Parser::IsTerminateToken(bool fAllowIn) { return (m_token.tk == tkRCurly || m_token.tk == tkRBrack || m_token.tk == tkRParen || m_token.tk == tkSColon || m_token.tk == tkColon || m_token.tk == tkComma || m_token.tk == tkLimKwd || (m_token.tk == tkIN && fAllowIn) || this->GetScanner()->FHadNewLine()); } /*************************************************************************** Parse an optional sub expression returning null if there was no expression. Checks for no expression by looking for a token that can follow an Expression grammar production. ***************************************************************************/ template bool Parser::ParseOptionalExpr(ParseNodePtr* pnode, bool fUnaryOrParen, int oplMin, BOOL *pfCanAssign, BOOL fAllowIn, BOOL fAllowEllipsis, _Inout_opt_ IdentToken* pToken) { *pnode = nullptr; if (IsTerminateToken(!fAllowIn)) { return false; } IdentToken token; ParseNodePtr pnodeT = ParseExpr(oplMin, pfCanAssign, fAllowIn, fAllowEllipsis, nullptr /*pNameHint*/, nullptr /*pHintLength*/, nullptr /*pShortNameOffset*/, &token, fUnaryOrParen); // Detect nested function escapes of the pattern "return function(){...}" or "yield function(){...}". // Doing so in the parser allows us to disable stack-nested-functions in common cases where an escape // is not detected at byte code gen time because of deferred parsing. this->MarkEscapingRef(pnodeT, &token); if (pToken) { *pToken = token; } *pnode = pnodeT; return true; } /*************************************************************************** Parse a sub expression. 'fAllowIn' indicates if the 'in' operator should be allowed in the initializing expression ( it is not allowed in the context of the first expression in a 'for' loop). ***************************************************************************/ template ParseNodePtr Parser::ParseExpr(int oplMin, BOOL *pfCanAssign, BOOL fAllowIn, BOOL fAllowEllipsis, LPCOLESTR pNameHint, uint32 *pHintLength, uint32 *pShortNameOffset, _Inout_opt_ IdentToken* pToken, bool fUnaryOrParen, _Inout_opt_ bool* pfLikelyPattern, _Inout_opt_ charcount_t *plastRParen) { Assert(pToken == nullptr || pToken->tk == tkNone); // Must be empty initially int opl; OpCode nop; charcount_t ichMin; ParseNodePtr pnode = nullptr; ParseNodePtr pnodeT = nullptr; BOOL fCanAssign = TRUE; bool assignmentStmt = false; bool fIsDotOrIndex = false; IdentToken term; RestorePoint termStart; uint32 hintLength = 0; uint32 hintOffset = 0; BOOL fLikelyPattern = FALSE; ParserState parserState; if (pHintLength != nullptr) { hintLength = *pHintLength; } if (pShortNameOffset != nullptr) { hintOffset = *pShortNameOffset; } EnsureStackAvailable(); // Storing the state here as we need to restore this state back when we need to reparse the grammar under lambda syntax. CaptureState(&parserState); this->GetScanner()->Capture(&termStart); bool savedDeferredInitError = m_hasDeferredShorthandInitError; m_hasDeferredShorthandInitError = false; // Is the current token a unary operator? if (this->GetHashTbl()->TokIsUnop(m_token.tk, &opl, &nop) && nop != knopNone) { IdentToken operandToken; ichMin = this->GetScanner()->IchMinTok(); if (nop == knopYield) { if (!this->GetScanner()->YieldIsKeywordRegion() || oplMin > opl) { // The case where 'yield' is scanned as a keyword (tkYIELD) but the scanner // is not treating yield as a keyword (!this->GetScanner()->YieldIsKeywordRegion()) occurs // in strict mode non-generator function contexts. // // That is, 'yield' is a keyword because of strict mode, but YieldExpression // is not a grammar production outside of generator functions. // // Otherwise it is an error for a yield to appear in the context of a higher level // binding operator, be it unary or binary. Error(ERRsyntax); } if (m_currentScope->GetScopeType() == ScopeType_Parameter || (m_currentScope->GetScopeType() == ScopeType_Block && m_currentScope->GetEnclosingScope()->GetScopeType() == ScopeType_Parameter)) // Check whether this is a class definition inside param scope { Error(ERRsyntax); } } else if (nop == knopAwait) { if (!this->GetScanner()->AwaitIsKeywordRegion() || m_currentScope->GetScopeType() == ScopeType_Parameter || (m_currentScope->GetScopeType() == ScopeType_Block && m_currentScope->GetEnclosingScope()->GetScopeType() == ScopeType_Parameter)) // Check whether this is a class definition inside param scope { // As with the 'yield' keyword, the case where 'await' is scanned as a keyword (tkAWAIT) // but the scanner is not treating await as a keyword (!this->GetScanner()->AwaitIsKeyword()) // occurs in strict mode non-async function contexts. // // That is, 'await' is a keyword because of strict mode, but AwaitExpression // is not a grammar production outside of async functions. // // Further, await expressions are disallowed within parameter scopes. Error(ERRBadAwait); } } this->GetScanner()->Scan(); if (m_token.tk == tkEllipsis) { // ... cannot have a unary prefix. Error(ERRUnexpectedEllipsis); } if (nop == knopYield && !this->GetScanner()->FHadNewLine() && m_token.tk == tkStar) { this->GetScanner()->Scan(); nop = knopYieldStar; } if (nop == knopYield) { if (!ParseOptionalExpr(&pnodeT, false, opl, NULL, fAllowIn, fAllowEllipsis)) { nop = knopYieldLeaf; if (buildAST) { pnode = CreateNodeForOpT(ichMin, this->GetScanner()->IchLimTok()); } } } else if (nop == knopAwait && m_token.tk == tkColon) { Error(ERRAwaitAsLabelInAsync); } else { // Disallow spread after a unary operator. pnodeT = ParseExpr(opl, &fCanAssign, TRUE, FALSE, nullptr /*hint*/, nullptr /*hintLength*/, nullptr /*hintOffset*/, &operandToken, true, nullptr, plastRParen); } if (nop != knopYieldLeaf) { if ((nop == knopIncPre || nop == knopDecPre) && (m_token.tk != tkDArrow)) { if (!fCanAssign && (m_sourceContextInfo ? !PHASE_OFF_RAW(Js::EarlyReferenceErrorsPhase, m_sourceContextInfo->sourceContextId, GetCurrentFunctionNode()->functionId) : !PHASE_OFF1(Js::EarlyReferenceErrorsPhase))) { Error(JSERR_CantAssignTo); } TrackAssignment(pnodeT, &operandToken); if (buildAST) { if (IsStrictMode() && pnodeT->nop == knopName) { CheckStrictModeEvalArgumentsUsage(pnodeT->AsParseNodeName()->pid); } } else { if (IsStrictMode() && operandToken.tk == tkID) { CheckStrictModeEvalArgumentsUsage(operandToken.pid); } } } else if (nop == knopEllipsis) { if (!fAllowEllipsis) { DeferOrEmitPotentialSpreadError(pnodeT); } } else if (m_token.tk == tkExpo) { //Unary operator on the left hand-side of ** is unexpected, except ++, -- or ... Error(ERRInvalidUseofExponentiationOperator); } if (buildAST) { //Do not do the folding for Asm in case of KnopPos as we need this to determine the type if (nop == knopPos && (pnodeT->nop == knopInt || pnodeT->nop == knopFlt) && !this->m_InAsmMode) { // Fold away a unary '+' on a number. pnode = pnodeT; } else if (nop == knopNeg && ((pnodeT->nop == knopInt && pnodeT->AsParseNodeInt()->lw != 0) || (pnodeT->nop == knopFlt && (pnodeT->AsParseNodeFloat()->dbl != 0 || this->m_InAsmMode)))) { // Fold a unary '-' on a number into the value of the number itself. pnode = pnodeT; if (pnode->nop == knopInt) { pnode->AsParseNodeInt()->lw = -pnode->AsParseNodeInt()->lw; } else { pnode->AsParseNodeFloat()->dbl = -pnode->AsParseNodeFloat()->dbl; } } else { pnode = CreateUniNode(nop, pnodeT); this->CheckArguments(pnode->AsParseNodeUni()->pnode1); } pnode->ichMin = ichMin; } if (nop == knopDelete) { if (IsStrictMode()) { if ((buildAST && pnode->AsParseNodeUni()->pnode1->IsUserIdentifier()) || (!buildAST && operandToken.tk == tkID && !this->IsSpecialName(operandToken.pid))) { Error(ERRInvalidDelete); } } if (buildAST) { ParseNodePtr pnode1 = pnode->AsParseNodeUni()->pnode1; if (m_currentNodeFunc) { if (pnode1->nop == knopDot || pnode1->nop == knopIndex) { // If we delete an arguments property, use the conservative, // heap-allocated arguments object. this->CheckArguments(pnode1->AsParseNodeBin()->pnode1); } } } } } fCanAssign = FALSE; } else { ichMin = this->GetScanner()->IchMinTok(); pnode = ParseTerm(TRUE, pNameHint, &hintLength, &hintOffset, &term, fUnaryOrParen, TRUE, &fCanAssign, IsES6DestructuringEnabled() ? &fLikelyPattern : nullptr, &fIsDotOrIndex, plastRParen); if (pfLikelyPattern != nullptr) { *pfLikelyPattern = !!fLikelyPattern; } if (m_token.tk == tkDArrow // If we have introduced shorthand error above in the ParseExpr, we need to reset if next token is the assignment. || (m_token.tk == tkAsg && oplMin <= koplAsg)) { m_hasDeferredShorthandInitError = false; } if (m_token.tk == tkAsg && oplMin <= koplAsg && fLikelyPattern) { this->GetScanner()->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) { this->SetHasDestructuringPattern(true); pnode = ConvertToPattern(pnode); } } if (buildAST) { pNameHint = NULL; if (pnode->nop == knopName) { IdentPtr pid = pnode->AsParseNodeName()->pid; pNameHint = pid->Psz(); hintLength = pid->Cch(); hintOffset = 0; } else if (pnode->nop == knopDot || pnode->nop == knopIndex) { if (CONFIG_FLAG(UseFullName)) { pNameHint = ConstructNameHint(pnode->AsParseNodeBin(), &hintLength, &hintOffset); } else { ParseNodePtr pnodeName = pnode; while (pnodeName->nop == knopDot) { pnodeName = pnodeName->AsParseNodeBin()->pnode2; } if (pnodeName->nop == knopName) { IdentPtr pid = pnode->AsParseNodeName()->pid; pNameHint = pid->Psz(); hintLength = pid->Cch(); hintOffset = 0; } } } } // Check for postfix unary operators. if (!this->GetScanner()->FHadNewLine() && (tkInc == m_token.tk || tkDec == m_token.tk)) { if (!fCanAssign && (m_sourceContextInfo ? !PHASE_OFF_RAW(Js::EarlyReferenceErrorsPhase, m_sourceContextInfo->sourceContextId, GetCurrentFunctionNode()->functionId) : !PHASE_OFF1(Js::EarlyReferenceErrorsPhase))) { Error(JSERR_CantAssignTo); } TrackAssignment(pnode, &term); fCanAssign = FALSE; if (buildAST) { if (IsStrictMode() && pnode->nop == knopName) { CheckStrictModeEvalArgumentsUsage(pnode->AsParseNodeName()->pid); } this->CheckArguments(pnode); pnode = CreateUniNode(tkInc == m_token.tk ? knopIncPost : knopDecPost, pnode); pnode->ichLim = this->GetScanner()->IchLimTok(); } else { if (IsStrictMode() && term.tk == tkID) { CheckStrictModeEvalArgumentsUsage(term.pid); } // This expression is not an identifier term.tk = tkNone; } this->GetScanner()->Scan(); } } // Process a sequence of operators and operands. for (;;) { if (!this->GetHashTbl()->TokIsBinop(m_token.tk, &opl, &nop) || nop == knopNone) { break; } if (!fAllowIn && nop == knopIn) { break; } Assert(opl != koplNo); if (opl == koplAsg) { if (m_token.tk != tkDArrow) { // Assignment operator. These are the only right associative // binary operators. We also need to special case the left // operand - it should only be a LeftHandSideExpression. Assert(ParseNode::Grfnop(nop) & fnopAsg || nop == knopFncDecl); TrackAssignment(pnode, &term); if (buildAST) { if (IsStrictMode() && pnode->nop == knopName) { CheckStrictModeEvalArgumentsUsage(pnode->AsParseNodeName()->pid); } // Assignment stmt of the form "this. = " if (nop == knopAsg && pnode->nop == knopDot && pnode->AsParseNodeBin()->pnode1->nop == knopName && pnode->AsParseNodeBin()->pnode1->AsParseNodeName()->pid == wellKnownPropertyPids._this && pnode->AsParseNodeBin()->pnode2->nop == knopName) { if (pnode->AsParseNodeBin()->pnode2->AsParseNodeName()->pid != wellKnownPropertyPids.__proto__) { assignmentStmt = true; } } } else { if (IsStrictMode() && term.tk == tkID) { CheckStrictModeEvalArgumentsUsage(term.pid); } } } if (opl < oplMin) { break; } if (m_token.tk != tkDArrow && !fCanAssign && (m_sourceContextInfo ? !PHASE_OFF_RAW(Js::EarlyReferenceErrorsPhase, m_sourceContextInfo->sourceContextId, GetCurrentFunctionNode()->functionId) : !PHASE_OFF1(Js::EarlyReferenceErrorsPhase))) { Error(JSERR_CantAssignTo); // No recovery necessary since this is a semantic, not structural, error. } } else if (opl == koplExpo) { // ** operator is right associative if (opl < oplMin) { break; } } else if (opl <= oplMin) { break; } // This expression is not an identifier term.tk = tkNone; // Precedence is high enough. Consume the operator token. this->GetScanner()->Scan(); fCanAssign = !!fLikelyPattern; // Special case the "?:" operator if (nop == knopQmark) { pnodeT = ParseExpr(koplAsg, NULL, fAllowIn); ChkCurTok(tkColon, ERRnoColon); ParseNodePtr pnodeT2 = ParseExpr(koplAsg, NULL, fAllowIn, 0, nullptr, nullptr, nullptr, nullptr, false, nullptr, plastRParen); if (buildAST) { pnode = CreateTriNode(nop, pnode, pnodeT, pnodeT2); this->CheckArguments(pnode->AsParseNodeTri()->pnode2); this->CheckArguments(pnode->AsParseNodeTri()->pnode3); } } else if (nop == knopFncDecl) { ushort flags = fFncLambda; size_t iecpMin = 0; bool isAsyncMethod = false; RestoreStateFrom(&parserState); this->GetScanner()->SeekTo(termStart); if (m_token.tk == tkID && m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.async && m_scriptContext->GetConfig()->IsES7AsyncAndAwaitEnabled()) { ichMin = this->GetScanner()->IchMinTok(); iecpMin = this->GetScanner()->IecpMinTok(); this->GetScanner()->Scan(); if ((m_token.tk == tkID || m_token.tk == tkLParen) && !this->GetScanner()->FHadNewLine()) { flags |= fFncAsync; isAsyncMethod = true; } else { this->GetScanner()->SeekTo(termStart); } } pnode = ParseFncDeclNoCheckScope(flags, SuperRestrictionState::Disallowed, nullptr, /* needsPIDOnRCurlyScan = */false, /* fUnaryOrParen = */ false, fAllowIn); if (isAsyncMethod) { pnode->AsParseNodeFnc()->cbStringMin = iecpMin; } // ArrowFunction/AsyncArrowFunction is part of AssignmentExpression, which should terminate the expression unless followed by a comma if (m_token.tk != tkComma && m_token.tk != tkIN) { if (!(IsTerminateToken(false))) { Error(ERRnoSemic); } break; } } else // a binary operator { if (nop == knopComma && m_token.tk == tkRParen) { // Trailing comma this->GetScanner()->Capture(&m_deferCommaErrorLoc); m_deferCommaError = true; break; } ParseNode* pnode1 = pnode; // Parse the operand, make a new node, and look for more IdentToken token; ParseNode* pnode2 = ParseExpr( opl, nullptr, fAllowIn, FALSE, pNameHint, &hintLength, &hintOffset, &token, false, nullptr, plastRParen); // Detect nested function escapes of the pattern "o.f = function(){...}" or "o[s] = function(){...}". // Doing so in the parser allows us to disable stack-nested-functions in common cases where an escape // is not detected at byte code gen time because of deferred parsing. if (fIsDotOrIndex && nop == knopAsg) { this->MarkEscapingRef(pnodeT, &token); } if (buildAST) { Assert(pnode2 != nullptr); if (pnode2->nop == knopFncDecl) { Assert(hintLength >= hintOffset); pnode2->AsParseNodeFnc()->hint = pNameHint; pnode2->AsParseNodeFnc()->hintLength = hintLength; pnode2->AsParseNodeFnc()->hintOffset = hintOffset; if (pnode1->nop == knopDot) { pnode2->AsParseNodeFnc()->isNameIdentifierRef = false; } else if (pnode1->nop == knopName) { PidRefStack *pidRef = pnode1->AsParseNodeName()->pid->GetTopRef(); pidRef->isFuncAssignment = true; } } else if (pnode2->nop == knopClassDecl && pnode1->nop == knopDot) { Assert(pnode2->AsParseNodeClass()->pnodeConstructor); if (!pnode2->AsParseNodeClass()->pnodeConstructor->pid) { pnode2->AsParseNodeClass()->pnodeConstructor->isNameIdentifierRef = false; } } else if (pnode1->nop == knopName && nop == knopIn) { PidRefStack* pidRef = pnode1->AsParseNodeName()->pid->GetTopRef(); pidRef->SetIsUsedInLdElem(true); } pnode = CreateBinNode(nop, pnode1, pnode2); } pNameHint = nullptr; } } if (buildAST) { if (!assignmentStmt) { // Don't set the flag for following nodes switch (pnode->nop) { case knopName: case knopInt: case knopFlt: case knopStr: case knopRegExp: case knopNull: case knopFalse: case knopTrue: break; default: if (m_currentNodeFunc) { m_currentNodeFunc->SetHasNonThisStmt(); } else if (m_currentNodeProg) { m_currentNodeProg->SetHasNonThisStmt(); } } } } m_hasDeferredShorthandInitError = m_hasDeferredShorthandInitError || savedDeferredInitError; if (NULL != pfCanAssign) { *pfCanAssign = fCanAssign; } // Pass back identifier if requested if (pToken && term.tk == tkID) { *pToken = term; } //Track "obj.a" assignment patterns here - Promote the Assignment state for the property's PID. // This includes =, += etc. if (pnode != NULL) { uint nodeType = ParseNode::Grfnop(pnode->nop); if (nodeType & fnopAsg) { if (nodeType & fnopBin) { ParseNodePtr lhs = pnode->AsParseNodeBin()->pnode1; Assert(lhs); if (lhs->nop == knopDot) { ParseNodePtr propertyNode = lhs->AsParseNodeBin()->pnode2; if (propertyNode->nop == knopName) { propertyNode->AsParseNodeName()->pid->PromoteAssignmentState(); } } } else if (nodeType & fnopUni) { // cases like obj.a++, ++obj.a ParseNodePtr lhs = pnode->AsParseNodeUni()->pnode1; if (lhs->nop == knopDot) { ParseNodePtr propertyNode = lhs->AsParseNodeBin()->pnode2; if (propertyNode->nop == knopName) { propertyNode->AsParseNodeName()->pid->PromoteAssignmentState(); } } } } } return pnode; } template void Parser::TrackAssignment(ParseNodePtr pnodeT, IdentToken* pToken) { if (buildAST) { Assert(pnodeT != nullptr); if (pnodeT->nop == knopName) { PidRefStack *ref = pnodeT->AsParseNodeName()->pid->GetTopRef(); Assert(ref); ref->isAsg = true; } } else { Assert(pToken != nullptr); if (pToken->tk == tkID) { PidRefStack *ref = pToken->pid->GetTopRef(); Assert(ref); ref->isAsg = true; } } } 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, 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 = currentFnc->functionId; if (!ref || (ref->GetScopeId() < blockId)) { ref = Anew(&m_nodeAllocator, PidRefStack); if (ref == nullptr) { Error(ERRnoMemory); } pid->PushPidRef(blockId, funcId, ref); } else if (m_reparsingLambdaParams) { // If we're reparsing params, then we may have pid refs left behind from the first pass. Make sure we're // working with the right ref at this point. ref = this->FindOrAddPidRef(pid, blockId, funcId); // Fix up the function ID if we're reparsing lambda parameters. ref->funcId = funcId; } return ref; } PidRefStack* Parser::FindOrAddPidRef(IdentPtr pid, int scopeId, Js::LocalFunctionId funcId) { PidRefStack *ref = pid->FindOrAddPidRef(&m_nodeAllocator, scopeId, funcId); if (ref == NULL) { Error(ERRnoMemory); } return ref; } void Parser::RemovePrevPidRef(IdentPtr pid, PidRefStack *ref) { PidRefStack *prevRef = pid->RemovePrevPidRef(ref); Assert(prevRef); if (prevRef->GetSym() == nullptr) { AllocatorDelete(ArenaAllocator, &m_nodeAllocator, prevRef); } } void Parser::SetPidRefsInScopeDynamic(IdentPtr pid, int blockId) { PidRefStack *ref = pid->GetTopRef(); while (ref && ref->GetScopeId() >= blockId) { ref->SetDynamicBinding(); ref = ref->prev; } } ParseNodeBlock* Parser::GetFunctionBlock() { Assert(m_currentBlockInfo != nullptr); return m_currentBlockInfo->pBlockInfoFunction->pnodeBlock; } ParseNodeBlock* Parser::GetCurrentBlock() { return m_currentBlockInfo != nullptr ? m_currentBlockInfo->pnodeBlock : nullptr; } BlockInfoStack* Parser::GetCurrentBlockInfo() { return m_currentBlockInfo; } BlockInfoStack* Parser::GetCurrentFunctionBlockInfo() { return m_currentBlockInfo->pBlockInfoFunction; } /*************************************************************************** Parse a variable declaration. 'fAllowIn' indicates if the 'in' operator should be allowed in the initializing expression ( it is not allowed in the context of the first expression in a 'for' loop). ***************************************************************************/ template ParseNodePtr Parser::ParseVariableDeclaration( tokens declarationType, charcount_t ichMin, BOOL fAllowIn/* = TRUE*/, BOOL* pfForInOk/* = nullptr*/, BOOL singleDefOnly/* = FALSE*/, BOOL allowInit/* = TRUE*/, BOOL isTopVarParse/* = TRUE*/, BOOL isFor/* = FALSE*/, BOOL* nativeForOk /*= nullptr*/) { ParseNodePtr pnodeThis = nullptr; ParseNodePtr pnodeInit; ParseNodePtr pnodeList = nullptr; ParseNodePtr *lastNodeRef = nullptr; LPCOLESTR pNameHint = nullptr; uint32 nameHintLength = 0; uint32 nameHintOffset = 0; Assert(declarationType == tkVAR || declarationType == tkCONST || declarationType == tkLET); for (;;) { if (IsES6DestructuringEnabled() && IsPossiblePatternStart()) { pnodeThis = ParseDestructuredLiteral(declarationType, true, !!isTopVarParse, DIC_None, !!fAllowIn, pfForInOk, nativeForOk); if (pnodeThis != nullptr) { pnodeThis->ichMin = ichMin; pnodeThis->SetIsPatternDeclaration(); } } else { if (m_token.tk != tkID) { IdentifierExpectedError(m_token); } IdentPtr pid = m_token.GetIdentifier(this->GetHashTbl()); Assert(pid); pNameHint = pid->Psz(); nameHintLength = pid->Cch(); nameHintOffset = 0; if (pid == wellKnownPropertyPids.let && (declarationType == tkCONST || declarationType == tkLET)) { Error(ERRLetIDInLexicalDecl, pnodeThis); } if (declarationType == tkVAR) { pnodeThis = CreateVarDeclNode(pid, STVariable); } else if (declarationType == tkCONST) { pnodeThis = CreateBlockScopedDeclNode(pid, knopConstDecl); CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, Const, m_scriptContext); } else { pnodeThis = CreateBlockScopedDeclNode(pid, knopLetDecl); CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, Let, m_scriptContext); } if (pid == wellKnownPropertyPids.arguments) { // This var declaration may change the way an 'arguments' identifier in the function is resolved if (declarationType == tkVAR) { GetCurrentFunctionNode()->grfpn |= PNodeFlags::fpnArguments_varDeclaration; } else { if (GetCurrentBlockInfo()->pnodeBlock->blockType == Function) { // Only override arguments if we are at the function block level. GetCurrentFunctionNode()->grfpn |= PNodeFlags::fpnArguments_overriddenByDecl; } } } if (pnodeThis) { pnodeThis->ichMin = ichMin; } this->GetScanner()->Scan(); if (m_token.tk == tkAsg) { if (!allowInit) { Error(ERRUnexpectedDefault); } if (pfForInOk && (declarationType == tkLET || declarationType == tkCONST || IsStrictMode())) { *pfForInOk = FALSE; } this->GetScanner()->Scan(); pnodeInit = ParseExpr(koplCma, nullptr, fAllowIn, FALSE, pNameHint, &nameHintLength, &nameHintOffset); if (buildAST) { AnalysisAssert(pnodeThis); pnodeThis->AsParseNodeVar()->pnodeInit = pnodeInit; pnodeThis->ichLim = pnodeInit->ichLim; if (pnodeInit->nop == knopFncDecl) { Assert(nameHintLength >= nameHintOffset); pnodeInit->AsParseNodeFnc()->hint = pNameHint; pnodeInit->AsParseNodeFnc()->hintLength = nameHintLength; pnodeInit->AsParseNodeFnc()->hintOffset = nameHintOffset; pnodeThis->AsParseNodeVar()->pid->GetTopRef()->isFuncAssignment = true; } else { this->CheckArguments(pnodeInit); } pNameHint = nullptr; } //Track var a =, let a= , const a = // This is for FixedFields Constant Heuristics if (pnodeThis && pnodeThis->AsParseNodeVar()->pnodeInit != nullptr) { pnodeThis->AsParseNodeVar()->sym->PromoteAssignmentState(); } } else if (declarationType == tkCONST /*pnodeThis->nop == knopConstDecl*/ && !singleDefOnly && !(isFor && TokIsForInOrForOf())) { Error(ERRUninitializedConst); } if (m_currentNodeFunc && pnodeThis && pnodeThis->AsParseNodeVar()->sym->GetIsFormal()) { m_currentNodeFunc->SetHasAnyWriteToFormals(true); } } if (singleDefOnly) { return pnodeThis; } if (buildAST) { AddToNodeListEscapedUse(&pnodeList, &lastNodeRef, pnodeThis); } if (m_token.tk != tkComma) { return pnodeList; } if (pfForInOk) { // don't allow "for (var a, b in c)" *pfForInOk = FALSE; } this->GetScanner()->Scan(); ichMin = this->GetScanner()->IchMinTok(); } } /*************************************************************************** Parse try-catch-finally statement ***************************************************************************/ // The try-catch-finally tree nests the try-catch within a try-finally. // This matches the new runtime implementation. template ParseNodeStmt * Parser::ParseTryCatchFinally() { this->m_tryCatchOrFinallyDepth++; ParseNodeTry * pnodeT = ParseTry(); ParseNodeTryCatch * pnodeTC = nullptr; StmtNest stmt; bool hasCatch = false; if (tkCATCH == m_token.tk) { hasCatch = true; if (buildAST) { pnodeTC = CreateNodeForOpT(); pnodeT->pnodeOuter = pnodeTC; pnodeTC->pnodeTry = pnodeT; } PushStmt(&stmt, pnodeTC, knopTryCatch, nullptr); ParseNodeCatch * pnodeCatch = ParseCatch(); if (buildAST) { pnodeTC->pnodeCatch = pnodeCatch; } PopStmt(&stmt); } if (tkFINALLY != m_token.tk) { if (!hasCatch) { Error(ERRnoCatch); } Assert(!buildAST || pnodeTC); this->m_tryCatchOrFinallyDepth--; return pnodeTC; } ParseNodeTryFinally * pnodeTF = nullptr; if (buildAST) { pnodeTF = CreateNodeForOpT(); } PushStmt(&stmt, pnodeTF, knopTryFinally, nullptr); ParseNodeFinally * pnodeFinally = ParseFinally(); if (buildAST) { if (!hasCatch) { pnodeTF->pnodeTry = pnodeT; pnodeT->pnodeOuter = pnodeTF; } else { pnodeTF->pnodeTry = CreateNodeForOpT(); pnodeTF->pnodeTry->pnodeOuter = pnodeTF; pnodeTF->pnodeTry->pnodeBody = pnodeTC; pnodeTC->pnodeOuter = pnodeTF->pnodeTry; } pnodeTF->pnodeFinally = pnodeFinally; } PopStmt(&stmt); this->m_tryCatchOrFinallyDepth--; return pnodeTF; } template ParseNodeTry * Parser::ParseTry() { ParseNodeTry * pnode = nullptr; StmtNest stmt; Assert(tkTRY == m_token.tk); if (buildAST) { pnode = CreateNodeForOpT(); } this->GetScanner()->Scan(); if (tkLCurly != m_token.tk) { Error(ERRnoLcurly); } PushStmt(&stmt, pnode, knopTry, nullptr); ParseNodePtr pnodeBody = ParseStatement(); if (buildAST) { pnode->pnodeBody = pnodeBody; if (pnode->pnodeBody) pnode->ichLim = pnode->pnodeBody->ichLim; } PopStmt(&stmt); return pnode; } template ParseNodeFinally * Parser::ParseFinally() { ParseNodeFinally * pnode = nullptr; StmtNest stmt; Assert(tkFINALLY == m_token.tk); if (buildAST) { pnode = CreateNodeForOpT(); } this->GetScanner()->Scan(); if (tkLCurly != m_token.tk) { Error(ERRnoLcurly); } PushStmt(&stmt, pnode, knopFinally, nullptr); ParseNodePtr pnodeBody = ParseStatement(); if (buildAST) { pnode->pnodeBody = pnodeBody; if (!pnode->pnodeBody) // Will only occur due to error correction. pnode->pnodeBody = CreateNodeForOpT(); else pnode->ichLim = pnode->pnodeBody->ichLim; } PopStmt(&stmt); return pnode; } template ParseNodeCatch * Parser::ParseCatch() { ParseNodePtr *ppnodeExprScopeSave = nullptr; ParseNodeCatch * pnode = nullptr; ParseNodeBlock * pnodeCatchScope = nullptr; StmtNest stmt; IdentPtr pidCatch = nullptr; if (tkCATCH == m_token.tk) { charcount_t ichMin; if (buildAST) { ichMin = this->GetScanner()->IchMinTok(); } this->GetScanner()->Scan(); //catch ChkCurTok(tkLParen, ERRnoLparen); //catch( bool isPattern = false; if (tkID != m_token.tk) { isPattern = IsES6DestructuringEnabled() && IsPossiblePatternStart(); if (!isPattern) { IdentifierExpectedError(m_token); } } if (buildAST) { pnode = CreateNodeForOpT(ichMin); pnode->pnodeNext = nullptr; PushStmt(&stmt, pnode, knopCatch, nullptr); } pnodeCatchScope = StartParseBlock(PnodeBlockType::Regular, isPattern ? ScopeType_CatchParamPattern : ScopeType_Catch); if (buildAST) { // Add this catch to the current scope list. if (m_ppnodeExprScope) { Assert(*m_ppnodeExprScope == nullptr); *m_ppnodeExprScope = pnode; m_ppnodeExprScope = &pnode->pnodeNext; } else { Assert(m_ppnodeScope); Assert(*m_ppnodeScope == nullptr); *m_ppnodeScope = pnode; m_ppnodeScope = &pnode->pnodeNext; } // Keep a list of function expressions (not declarations) at this scope. ppnodeExprScopeSave = m_ppnodeExprScope; m_ppnodeExprScope = &pnode->pnodeScopes; pnode->pnodeScopes = nullptr; } if (isPattern) { ParseNodePtr pnodePattern = ParseDestructuredLiteral(tkLET, true /*isDecl*/, true /*topLevel*/, DIC_ForceErrorOnInitializer); if (buildAST) { pnode->SetParam(CreateParamPatternNode(pnodePattern)); Scope *scope = pnodeCatchScope->scope; pnode->scope = scope; } } else { if (IsStrictMode()) { IdentPtr pid = m_token.GetIdentifier(this->GetHashTbl()); if (pid == wellKnownPropertyPids.eval) { Error(ERREvalUsage); } else if (pid == wellKnownPropertyPids.arguments) { Error(ERRArgsUsage); } } pidCatch = m_token.GetIdentifier(this->GetHashTbl()); PidRefStack *ref = this->FindOrAddPidRef(pidCatch, GetCurrentBlock()->blockId, GetCurrentFunctionNode()->functionId); ParseNodeName * pnodeParam = CreateNameNode(pidCatch); pnodeParam->SetSymRef(ref); const char16 *name = reinterpret_cast(pidCatch->Psz()); int nameLength = pidCatch->Cch(); SymbolName const symName(name, nameLength); Symbol *sym = Anew(&m_nodeAllocator, Symbol, symName, pnodeParam, STVariable); if (sym == nullptr) { Error(ERRnoMemory); } sym->SetPid(pidCatch); Assert(ref->GetSym() == nullptr); ref->SetSym(sym); Scope *scope = pnodeCatchScope->scope; scope->AddNewSymbol(sym); if (buildAST) { pnode->SetParam(pnodeParam); pnode->scope = scope; } this->GetScanner()->Scan(); } charcount_t ichLim; if (buildAST) { ichLim = this->GetScanner()->IchLimTok(); } ChkCurTok(tkRParen, ERRnoRparen); //catch(id[:expr]) if (tkLCurly != m_token.tk) { Error(ERRnoLcurly); } ParseNodePtr pnodeBody = ParseStatement(); //catch(id[:expr]) {block} if (buildAST) { pnode->pnodeBody = pnodeBody; pnode->ichLim = ichLim; } if (pnodeCatchScope != nullptr) { FinishParseBlock(pnodeCatchScope); } if (pnodeCatchScope->GetCallsEval() || pnodeCatchScope->GetChildCallsEval()) { GetCurrentBlock()->SetChildCallsEval(true); } if (buildAST) { PopStmt(&stmt); // Restore the lists of function expression scopes. Assert(m_ppnodeExprScope); Assert(*m_ppnodeExprScope == nullptr); m_ppnodeExprScope = ppnodeExprScopeSave; } } return pnode; } template ParseNodeCase * Parser::ParseCase(ParseNodePtr *ppnodeBody) { ParseNodeCase * pnodeT = nullptr; charcount_t ichMinT = this->GetScanner()->IchMinTok(); this->GetScanner()->Scan(); ParseNodePtr pnodeExpr = ParseExpr(); charcount_t ichLim = this->GetScanner()->IchLimTok(); ChkCurTok(tkColon, ERRnoColon); if (buildAST) { pnodeT = CreateNodeForOpT(ichMinT); pnodeT->pnodeExpr = pnodeExpr; pnodeT->ichLim = ichLim; } ParseStmtList(ppnodeBody); return pnodeT; } /*************************************************************************** Parse a single statement. Digest a trailing semicolon. ***************************************************************************/ template ParseNodePtr Parser::ParseStatement() { ParseNodePtr pnode = nullptr; LabelId* pLabelIdList = nullptr; charcount_t ichMin = 0; size_t iecpMin = 0; StmtNest stmt; StmtNest *pstmt; BOOL fForInOrOfOkay; BOOL fCanAssign; IdentPtr pid; uint fnop; bool expressionStmt = false; bool isAsyncMethod = false; bool labelledStatement = false; tokens tok; #if EXCEPTION_RECOVERY ParseNodeTryCatch * pParentTryCatch = nullptr; ParseNodeBlock * pTryBlock = nullptr; ParseNodeTry * pTry = nullptr; ParseNodeBlock * pParentTryCatchBlock = nullptr; StmtNest stmtTryCatchBlock; StmtNest stmtTryCatch; StmtNest stmtTry; StmtNest stmtTryBlock; #endif if (buildAST) { #if EXCEPTION_RECOVERY if (Js::Configuration::Global.flags.SwallowExceptions) { // If we're swallowing exceptions, surround this statement with a try/catch block: // // Before: x.y = 3; // After: try { x.y = 3; } catch(__ehobj) { } // // This is done to force the runtime to recover from exceptions at the most granular // possible point. Recovering from EH dramatically improves coverage of testing via // fault injection. // create and push the try-catch node pParentTryCatchBlock = CreateBlockNode(); PushStmt(&stmtTryCatchBlock, pParentTryCatchBlock, knopBlock, nullptr); pParentTryCatch = CreateNodeForOpT(); PushStmt(&stmtTryCatch, pParentTryCatch, knopTryCatch, nullptr); // create and push a try node pTry = CreateNodeForOpT(); PushStmt(&stmtTry, pTry, knopTry, nullptr); pTryBlock = CreateBlockNode(); PushStmt(&stmtTryBlock, pTryBlock, knopBlock, nullptr); // these nodes will be closed after the statement is parsed. } #endif // EXCEPTION_RECOVERY } EnsureStackAvailable(); LRestart: tok = m_token.tk; switch (tok) { case tkEOF: if (labelledStatement) { Error(ERRLabelFollowedByEOF); } if (buildAST) { pnode = nullptr; } break; case tkFUNCTION: { LFunctionStatement: if (m_grfscr & fscrDeferredFncExpression) { // The top-level deferred function body was defined by a function expression whose parsing was deferred. We are now // parsing it, so unset the flag so that any nested functions are parsed normally. This flag is only applicable the // first time we see it. m_grfscr &= ~fscrDeferredFncExpression; pnode = ParseFncDeclNoCheckScope(isAsyncMethod ? fFncAsync : fFncNoFlgs); } else { pnode = ParseFncDeclCheckScope(fFncDeclaration | (isAsyncMethod ? fFncAsync : fFncNoFlgs)); } Assert(pnode != nullptr); ParseNodeFnc* pNodeFnc = (ParseNodeFnc*)pnode; if (labelledStatement) { if (IsStrictMode()) { Error(ERRFunctionAfterLabelInStrict); } else if (pNodeFnc->IsAsync()) { Error(ERRLabelBeforeAsyncFncDeclaration); } else if (pNodeFnc->IsGenerator()) { Error(ERRLabelBeforeGeneratorDeclaration); } } if (isAsyncMethod) { pnode->AsParseNodeFnc()->cbStringMin = iecpMin; } break; } case tkCLASS: if (labelledStatement) { Error(ERRLabelBeforeClassDeclaration); } else if (m_scriptContext->GetConfig()->IsES6ClassAndExtendsEnabled()) { pnode = ParseClassDecl(TRUE, nullptr, nullptr, nullptr); } else { goto LDefaultToken; } break; case tkID: case tkLET: if (m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.let) { // We see "let" at the start of a statement. This could either be a declaration or an identifier // reference. The next token determines which. RestorePoint parsedLet; this->GetScanner()->Capture(&parsedLet); ichMin = this->GetScanner()->IchMinTok(); this->GetScanner()->Scan(); if (labelledStatement) { if (!this->GetScanner()->FHadNewLine() || m_token.tk == tkLBrack) { // In the case where a label is followed by a let, we want to fail when parsing if there is no new line after let, // otherwise fail at runtime as let will be viewed as undefined. A left bracket after a let signifies a syntax error regardless. Error(ERRLabelBeforeLexicalDeclaration); } } else if (this->NextTokenConfirmsLetDecl()) { pnode = ParseVariableDeclaration(tkLET, ichMin); goto LNeedTerminator; } this->GetScanner()->SeekTo(parsedLet); } else if (m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.async && m_scriptContext->GetConfig()->IsES7AsyncAndAwaitEnabled()) { RestorePoint parsedAsync; this->GetScanner()->Capture(&parsedAsync); ichMin = this->GetScanner()->IchMinTok(); iecpMin = this->GetScanner()->IecpMinTok(); this->GetScanner()->Scan(); if (m_token.tk == tkFUNCTION && !this->GetScanner()->FHadNewLine()) { isAsyncMethod = true; goto LFunctionStatement; } this->GetScanner()->SeekTo(parsedAsync); } goto LDefaultToken; case tkCONST: if (labelledStatement) { Error(ERRLabelBeforeLexicalDeclaration); } ichMin = this->GetScanner()->IchMinTok(); this->GetScanner()->Scan(); pnode = ParseVariableDeclaration(tok, ichMin); goto LNeedTerminator; case tkVAR: ichMin = this->GetScanner()->IchMinTok(); this->GetScanner()->Scan(); pnode = ParseVariableDeclaration(tok, ichMin); goto LNeedTerminator; case tkFOR: { ParseNodeBlock * pnodeBlock = nullptr; ParseNodePtr *ppnodeScopeSave = nullptr; ParseNodePtr *ppnodeExprScopeSave = nullptr; ichMin = this->GetScanner()->IchMinTok(); ChkNxtTok(tkLParen, ERRnoLparen); pnodeBlock = StartParseBlock(PnodeBlockType::Regular, ScopeType_Block); if (buildAST) { PushFuncBlockScope(pnodeBlock, &ppnodeScopeSave, &ppnodeExprScopeSave); } RestorePoint startExprOrIdentifier; fForInOrOfOkay = TRUE; fCanAssign = TRUE; tok = m_token.tk; BOOL nativeForOkay = TRUE; ParseNodePtr pnodeT; switch (tok) { case tkID: if (m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.let) { // We see "let" in the init part of a for loop. This could either be a declaration or an identifier // reference. The next token determines which. RestorePoint parsedLet; this->GetScanner()->Capture(&parsedLet); auto ichMinInner = this->GetScanner()->IchMinTok(); this->GetScanner()->Scan(); if (IsPossiblePatternStart()) { this->GetScanner()->Capture(&startExprOrIdentifier); } if (this->NextTokenConfirmsLetDecl() && m_token.tk != tkIN) { pnodeT = ParseVariableDeclaration(tkLET, ichMinInner , /*fAllowIn = */FALSE , /*pfForInOk = */&fForInOrOfOkay , /*singleDefOnly*/FALSE , /*allowInit*/TRUE , /*isTopVarParse*/TRUE , /*isFor*/TRUE , &nativeForOkay); break; } this->GetScanner()->SeekTo(parsedLet); } goto LDefaultTokenFor; case tkLET: case tkCONST: case tkVAR: { auto ichMinInner = this->GetScanner()->IchMinTok(); this->GetScanner()->Scan(); if (IsPossiblePatternStart()) { this->GetScanner()->Capture(&startExprOrIdentifier); } pnodeT = ParseVariableDeclaration(tok, ichMinInner , /*fAllowIn = */FALSE , /*pfForInOk = */&fForInOrOfOkay , /*singleDefOnly*/FALSE , /*allowInit*/TRUE , /*isTopVarParse*/TRUE , /*isFor*/TRUE , &nativeForOkay); } break; case tkSColon: pnodeT = nullptr; fForInOrOfOkay = FALSE; break; default: { LDefaultTokenFor: RestorePoint exprStart; tokens beforeToken = tok; this->GetScanner()->Capture(&exprStart); if (IsPossiblePatternStart()) { this->GetScanner()->Capture(&startExprOrIdentifier); } bool fLikelyPattern = false; if (IsES6DestructuringEnabled() && (beforeToken == tkLBrack || beforeToken == tkLCurly)) { pnodeT = ParseExpr(koplNo, &fCanAssign, /*fAllowIn = */FALSE, /*fAllowEllipsis*/FALSE, /*pHint*/nullptr, /*pHintLength*/nullptr, /*pShortNameOffset*/nullptr, /*pToken*/nullptr, /**fUnaryOrParen*/false, &fLikelyPattern); } else { pnodeT = ParseExpr(koplNo, &fCanAssign, /*fAllowIn = */FALSE); } // We would veryfiy the grammar as destructuring grammar only when for..in/of case. As in the native for loop case the above ParseExpr call // has already converted them appropriately. if (fLikelyPattern && TokIsForInOrForOf()) { this->GetScanner()->SeekTo(exprStart); ParseDestructuredLiteralWithScopeSave(tkNone, false/*isDecl*/, false /*topLevel*/, DIC_None, false /*allowIn*/); if (buildAST) { pnodeT = ConvertToPattern(pnodeT); } } if (buildAST) { Assert(pnodeT); pnodeT->isUsed = false; } } break; } if (TokIsForInOrForOf()) { bool isForOf = (m_token.tk != tkIN); Assert(!isForOf || (m_token.tk == tkID && m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.of)); if ((buildAST && nullptr == pnodeT) || !fForInOrOfOkay) { if (isForOf) { Error(ERRForOfNoInitAllowed); } else { Error(ERRForInNoInitAllowed); } } if (!fCanAssign && (m_sourceContextInfo ? !PHASE_OFF_RAW(Js::EarlyReferenceErrorsPhase, m_sourceContextInfo->sourceContextId, GetCurrentFunctionNode()->functionId) : !PHASE_OFF1(Js::EarlyReferenceErrorsPhase))) { Error(ERRInvalidLHSInFor); } this->GetScanner()->Scan(); ParseNodePtr pnodeObj = ParseExpr(isForOf ? koplCma : koplNo); charcount_t ichLim = this->GetScanner()->IchLimTok(); ChkCurTok(tkRParen, ERRnoRparen); ParseNodeForInOrForOf * pnodeForInOrForOf = nullptr; if (buildAST) { if (isForOf) { pnodeForInOrForOf = CreateNodeForOpT(ichMin); } else { pnodeForInOrForOf = CreateNodeForOpT(ichMin); } pnodeForInOrForOf->pnodeBlock = pnodeBlock; pnodeForInOrForOf->pnodeLval = pnodeT; pnodeForInOrForOf->pnodeObj = pnodeObj; pnodeForInOrForOf->ichLim = ichLim; TrackAssignment(pnodeT, nullptr); } PushStmt(&stmt, pnodeForInOrForOf, isForOf ? knopForOf : knopForIn, pLabelIdList); ParseNodePtr pnodeBody = ParseStatement(); if (buildAST) { pnodeForInOrForOf->pnodeBody = pnodeBody; pnode = pnodeForInOrForOf; } PopStmt(&stmt); } else { if (!nativeForOkay) { Error(ERRDestructInit); } ChkCurTok(tkSColon, ERRnoSemic); ParseNodePtr pnodeCond = nullptr; if (m_token.tk != tkSColon) { pnodeCond = ParseExpr(); if (m_token.tk != tkSColon) { Error(ERRnoSemic); } } tokens tk; tk = this->GetScanner()->Scan(); ParseNodePtr pnodeIncr = nullptr; if (tk != tkRParen) { pnodeIncr = ParseExpr(); if (pnodeIncr) { pnodeIncr->isUsed = false; } } charcount_t ichLim = this->GetScanner()->IchLimTok(); ChkCurTok(tkRParen, ERRnoRparen); ParseNodeFor * pnodeFor = nullptr; if (buildAST) { pnodeFor = CreateNodeForOpT(ichMin); pnodeFor->pnodeBlock = pnodeBlock; pnodeFor->pnodeInverted = nullptr; pnodeFor->pnodeInit = pnodeT; pnodeFor->pnodeCond = pnodeCond; pnodeFor->pnodeIncr = pnodeIncr; pnodeFor->ichLim = ichLim; } PushStmt(&stmt, pnodeFor, knopFor, pLabelIdList); ParseNodePtr pnodeBody = ParseStatement(); if (buildAST) { pnodeFor->pnodeBody = pnodeBody; pnode = pnodeFor; } PopStmt(&stmt); } if (buildAST) { PopFuncBlockScope(ppnodeScopeSave, ppnodeExprScopeSave); } FinishParseBlock(pnodeBlock); break; } case tkSWITCH: { BOOL fSeenDefault = FALSE; ParseNodeBlock * pnodeBlock = nullptr; ParseNodePtr *ppnodeScopeSave = nullptr; ParseNodePtr *ppnodeExprScopeSave = nullptr; ichMin = this->GetScanner()->IchMinTok(); ChkNxtTok(tkLParen, ERRnoLparen); ParseNodePtr pnodeVal = ParseExpr(); charcount_t ichLim = this->GetScanner()->IchLimTok(); ChkCurTok(tkRParen, ERRnoRparen); ChkCurTok(tkLCurly, ERRnoLcurly); ParseNodeSwitch * pnodeSwitch = nullptr; if (buildAST) { pnodeSwitch = CreateNodeForOpT(ichMin); } PushStmt(&stmt, pnodeSwitch, knopSwitch, pLabelIdList); pnodeBlock = StartParseBlock(PnodeBlockType::Regular, ScopeType_Block); ParseNodeCase ** ppnodeCase = nullptr; if (buildAST) { pnodeSwitch->pnodeVal = pnodeVal; pnodeSwitch->pnodeBlock = pnodeBlock; pnodeSwitch->ichLim = ichLim; PushFuncBlockScope(pnodeSwitch->pnodeBlock, &ppnodeScopeSave, &ppnodeExprScopeSave); pnodeSwitch->pnodeDefault = nullptr; ppnodeCase = &pnodeSwitch->pnodeCases; pnode = pnodeSwitch; } for (;;) { ParseNodeCase * pnodeCase; ParseNodePtr pnodeBody = nullptr; switch (m_token.tk) { default: goto LEndSwitch; case tkCASE: { pnodeCase = this->ParseCase(&pnodeBody); break; } case tkDEFAULT: if (fSeenDefault) { Error(ERRdupDefault); // No recovery necessary since this is a semantic, not structural, error } fSeenDefault = TRUE; charcount_t ichMinT = this->GetScanner()->IchMinTok(); this->GetScanner()->Scan(); charcount_t ichMinInner = this->GetScanner()->IchLimTok(); ChkCurTok(tkColon, ERRnoColon); if (buildAST) { pnodeCase = CreateNodeForOpT(ichMinT); pnodeSwitch->pnodeDefault = pnodeCase; pnodeCase->ichLim = ichMinInner; pnodeCase->pnodeExpr = nullptr; } ParseStmtList(&pnodeBody); break; } // Create a block node to contain the statement list for this case. // This helps us insert byte code to return the right value from // global/eval code. ParseNodeBlock * pnodeFakeBlock = CreateBlockNode(); if (buildAST) { if (pnodeBody) { pnodeFakeBlock->ichMin = pnodeCase->ichMin; pnodeFakeBlock->ichLim = pnodeCase->ichLim; pnodeCase->pnodeBody = pnodeFakeBlock; pnodeCase->pnodeBody->grfpn |= PNodeFlags::fpnSyntheticNode; // block is not a user specifier block pnodeCase->pnodeBody->pnodeStmt = pnodeBody; } else { pnodeCase->pnodeBody = nullptr; } *ppnodeCase = pnodeCase; ppnodeCase = &pnodeCase->pnodeNext; } } LEndSwitch: ChkCurTok(tkRCurly, ERRnoRcurly); if (buildAST) { *ppnodeCase = nullptr; PopFuncBlockScope(ppnodeScopeSave, ppnodeExprScopeSave); FinishParseBlock(pnode->AsParseNodeSwitch()->pnodeBlock); } else { FinishParseBlock(pnodeBlock); } PopStmt(&stmt); break; } case tkWHILE: { ichMin = this->GetScanner()->IchMinTok(); ChkNxtTok(tkLParen, ERRnoLparen); ParseNodePtr pnodeCond = ParseExpr(); charcount_t ichLim = this->GetScanner()->IchLimTok(); ChkCurTok(tkRParen, ERRnoRparen); ParseNodeWhile * pnodeWhile = nullptr; if (buildAST) { pnodeWhile = CreateNodeForOpT(ichMin); pnodeWhile->pnodeCond = pnodeCond; pnodeWhile->ichLim = ichLim; } bool stashedDisallowImportExportStmt = m_disallowImportExportStmt; m_disallowImportExportStmt = true; PushStmt(&stmt, pnodeWhile, knopWhile, pLabelIdList); ParseNodePtr pnodeBody = ParseStatement(); PopStmt(&stmt); if (buildAST) { pnodeWhile->pnodeBody = pnodeBody; pnode = pnodeWhile; } m_disallowImportExportStmt = stashedDisallowImportExportStmt; break; } case tkDO: { ParseNodeWhile * pnodeWhile = nullptr; if (buildAST) { pnodeWhile = CreateNodeForOpT(); } PushStmt(&stmt, pnodeWhile, knopDoWhile, pLabelIdList); this->GetScanner()->Scan(); bool stashedDisallowImportExportStmt = m_disallowImportExportStmt; m_disallowImportExportStmt = true; ParseNodePtr pnodeBody = ParseStatement(); m_disallowImportExportStmt = stashedDisallowImportExportStmt; PopStmt(&stmt); charcount_t ichMinT = this->GetScanner()->IchMinTok(); ChkCurTok(tkWHILE, ERRnoWhile); ChkCurTok(tkLParen, ERRnoLparen); ParseNodePtr pnodeCond = ParseExpr(); charcount_t ichLim = this->GetScanner()->IchLimTok(); ChkCurTok(tkRParen, ERRnoRparen); if (buildAST) { pnodeWhile->pnodeBody = pnodeBody; pnodeWhile->pnodeCond = pnodeCond; pnodeWhile->ichLim = ichLim; pnodeWhile->ichMin = ichMinT; pnode = pnodeWhile; } // REVIEW: Allow do...while statements to be embedded in other compound statements like if..else, or do..while? // goto LNeedTerminator; // For now just eat the trailing semicolon if present. if (m_token.tk == tkSColon) { if (pnode) { pnode->grfpn |= PNodeFlags::fpnExplicitSemicolon; } this->GetScanner()->Scan(); } else if (pnode) { pnode->grfpn |= PNodeFlags::fpnAutomaticSemicolon; } break; } case tkIF: { ichMin = this->GetScanner()->IchMinTok(); ChkNxtTok(tkLParen, ERRnoLparen); ParseNodePtr pnodeCond = ParseExpr(); ParseNodeIf * pnodeIf = nullptr; if (buildAST) { pnodeIf = CreateNodeForOpT(ichMin); pnodeIf->ichLim = this->GetScanner()->IchLimTok(); pnodeIf->pnodeCond = pnodeCond; } ChkCurTok(tkRParen, ERRnoRparen); bool stashedDisallowImportExportStmt = m_disallowImportExportStmt; m_disallowImportExportStmt = true; PushStmt(&stmt, pnodeIf, knopIf, pLabelIdList); ParseNodePtr pnodeTrue = ParseStatement(); ParseNodePtr pnodeFalse = nullptr; if (m_token.tk == tkELSE) { this->GetScanner()->Scan(); pnodeFalse = ParseStatement(); } if (buildAST) { pnodeIf->pnodeTrue = pnodeTrue; pnodeIf->pnodeFalse = pnodeFalse; pnode = pnodeIf; } PopStmt(&stmt); m_disallowImportExportStmt = stashedDisallowImportExportStmt; break; } case tkTRY: { ParseNodeBlock * pnodeBlock = CreateBlockNode(); pnodeBlock->grfpn |= PNodeFlags::fpnSyntheticNode; // block is not a user specifier block PushStmt(&stmt, pnodeBlock, knopBlock, pLabelIdList); ParseNodePtr pnodeStmt = ParseTryCatchFinally(); if (buildAST) { pnodeBlock->pnodeStmt = pnodeStmt; } PopStmt(&stmt); pnode = pnodeBlock; break; } case tkWITH: { if (IsStrictMode()) { Error(ERRES5NoWith); } if (m_currentNodeFunc) { GetCurrentFunctionNode()->SetHasWithStmt(); // Used by DeferNested } ichMin = this->GetScanner()->IchMinTok(); ChkNxtTok(tkLParen, ERRnoLparen); ParseNodePtr pnodeObj = ParseExpr(); if (!buildAST) { m_scopeCountNoAst++; } charcount_t ichLim = this->GetScanner()->IchLimTok(); ChkCurTok(tkRParen, ERRnoRparen); ParseNodeWith * pnodeWith = nullptr; if (buildAST) { pnodeWith = CreateNodeForOpT(ichMin); } PushStmt(&stmt, pnodeWith, knopWith, pLabelIdList); ParseNodePtr *ppnodeExprScopeSave = nullptr; if (buildAST) { pnodeWith->pnodeObj = pnodeObj; this->CheckArguments(pnodeWith->pnodeObj); if (m_ppnodeExprScope) { Assert(*m_ppnodeExprScope == nullptr); *m_ppnodeExprScope = pnodeWith; m_ppnodeExprScope = &pnodeWith->pnodeNext; } else { Assert(m_ppnodeScope); Assert(*m_ppnodeScope == nullptr); *m_ppnodeScope = pnodeWith; m_ppnodeScope = &pnodeWith->pnodeNext; } pnodeWith->pnodeNext = nullptr; pnodeWith->scope = nullptr; ppnodeExprScopeSave = m_ppnodeExprScope; m_ppnodeExprScope = &pnodeWith->pnodeScopes; pnodeWith->pnodeScopes = nullptr; pnodeWith->ichLim = ichLim; pnode = pnodeWith; } PushBlockInfo(CreateBlockNode()); PushDynamicBlock(); ParseNodePtr pnodeBody = ParseStatement(); if (buildAST) { pnode->AsParseNodeWith()->pnodeBody = pnodeBody; m_ppnodeExprScope = ppnodeExprScopeSave; } else { m_scopeCountNoAst--; } // The dynamic block is not stored in the actual parse tree and so will not // be visited by the byte code generator. Grab the callsEval flag off it and // pass on to outer block in case of: // with (...) eval(...); // i.e. blockless form of with bool callsEval = GetCurrentBlock()->GetCallsEval(); PopBlockInfo(); if (callsEval) { // be careful not to overwrite an existing true with false GetCurrentBlock()->SetCallsEval(true); } PopStmt(&stmt); break; } case tkLCurly: pnode = ParseBlock(pLabelIdList); break; case tkSColon: pnode = nullptr; this->GetScanner()->Scan(); break; case tkBREAK: if (buildAST) { pnode = CreateNodeForOpT(); } fnop = fnopBreak; goto LGetJumpStatement; case tkCONTINUE: if (buildAST) { pnode = CreateNodeForOpT(); } fnop = fnopContinue; LGetJumpStatement: this->GetScanner()->ScanForcingPid(); if (tkID == m_token.tk && !this->GetScanner()->FHadNewLine()) { // Labeled break or continue. pid = m_token.GetIdentifier(this->GetHashTbl()); if (buildAST) { ParseNodeJump * pnodeJump = pnode->AsParseNodeJump(); pnodeJump->hasExplicitTarget = true; pnodeJump->ichLim = this->GetScanner()->IchLimTok(); this->GetScanner()->Scan(); PushStmt(&stmt, pnodeJump, pnodeJump->nop, pLabelIdList); Assert(pnodeJump->grfnop == 0); for (pstmt = m_pstmtCur; nullptr != pstmt; pstmt = pstmt->pstmtOuter) { for (LabelId* label = pstmt->pLabelId; label != nullptr; label = label->next) { if (pid == label->pid) { // Found the label. Make sure we can use it. We can // break out of any statement, but we can only // continue loops. if (fnop == fnopContinue && !(pstmt->pnodeStmt->Grfnop() & fnop)) { Error(ERRbadContinue); } else { pstmt->pnodeStmt->grfnop |= fnop; pnodeJump->pnodeTarget = pstmt->pnodeStmt; } PopStmt(&stmt); goto LNeedTerminator; } } pnodeJump->grfnop |= (pstmt->pnodeStmt->Grfnop() & fnopCleanup); } } else { this->GetScanner()->Scan(); // Check if label is found within the current label id list. auto checkLabelList = [&](LabelId* list, StmtNest* checkStmtOp) { for (LabelId* pLabelId = list; pLabelId; pLabelId = pLabelId->next) { if (pid == pLabelId->pid) { // Found the label. Make sure we can use it. We can // break out of any statement, but we can only // continue loops. if (fnop == fnopContinue && !(ParseNode::Grfnop(checkStmtOp->op) & fnop)) { Error(ERRbadContinue); } return true; } } return false; }; if (checkLabelList(pLabelIdList, m_pstmtCur)) goto LNeedTerminator; for (pstmt = m_pstmtCur; pstmt; pstmt = pstmt->pstmtOuter) { if (checkLabelList(pstmt->pLabelId, pstmt)) goto LNeedTerminator; } } Error(ERRnoLabel); } else { // If we're doing a fast scan, we're not tracking labels, so we can't accurately do this analysis. // Let the thread that's doing the full parse detect the error, if there is one. if (!this->IsDoingFastScan()) { // Unlabeled break or continue. ParseNodeJump * pnodeJump = nullptr; if (buildAST) { pnodeJump = pnode->AsParseNodeJump(); pnodeJump->hasExplicitTarget = false; PushStmt(&stmt, pnodeJump, pnodeJump->nop, pLabelIdList); Assert(pnodeJump->grfnop == 0); } for (pstmt = m_pstmtCur; nullptr != pstmt; pstmt = pstmt->pstmtOuter) { if (buildAST) { AnalysisAssert(pstmt->pnodeStmt); if (pstmt->pnodeStmt->Grfnop() & fnop) { pstmt->pnodeStmt->grfnop |= fnop; pnodeJump->pnodeTarget = pstmt->pnodeStmt; PopStmt(&stmt); goto LNeedTerminator; } pnodeJump->grfnop |= (pstmt->pnodeStmt->Grfnop() & fnopCleanup); } else { if (ParseNode::Grfnop(pstmt->GetNop()) & fnop) { if (!pstmt->isDeferred) { AnalysisAssert(pstmt->pnodeStmt); pstmt->pnodeStmt->grfnop |= fnop; } goto LNeedTerminator; } } } Error(fnop == fnopBreak ? ERRbadBreak : ERRbadContinue); } goto LNeedTerminator; } case tkRETURN: { ParseNodeReturn * pnodeReturn; if (buildAST) { if (nullptr == m_currentNodeFunc || IsTopLevelModuleFunc()) { Error(ERRbadReturn); } pnodeReturn = CreateNodeForOpT(); } this->GetScanner()->Scan(); ParseNodePtr pnodeExpr = nullptr; ParseOptionalExpr(&pnodeExpr, true); // Class constructors have special semantics regarding return statements. // This might require a reference to 'this' if (GetCurrentFunctionNode()->IsClassConstructor()) { ReferenceSpecialName(wellKnownPropertyPids._this); } if (buildAST) { pnodeReturn->pnodeExpr = pnodeExpr; if (pnodeExpr) { this->CheckArguments(pnodeReturn->pnodeExpr); pnodeReturn->ichLim = pnodeReturn->pnodeExpr->ichLim; } // See if return should call finally PushStmt(&stmt, pnodeReturn, knopReturn, pLabelIdList); Assert(pnodeReturn->grfnop == 0); for (pstmt = m_pstmtCur; nullptr != pstmt; pstmt = pstmt->pstmtOuter) { if (pstmt->pnodeStmt->Grfnop() & fnopCleanup) { pnodeReturn->grfnop |= fnopCleanup; break; } } PopStmt(&stmt); pnode = pnodeReturn; } goto LNeedTerminator; } case tkTHROW: { if (buildAST) { pnode = CreateUniNode(knopThrow, nullptr); } this->GetScanner()->Scan(); ParseNodePtr pnode1 = nullptr; if (m_token.tk != tkSColon && m_token.tk != tkRCurly && !this->GetScanner()->FHadNewLine()) { pnode1 = ParseExpr(); } else { Error(ERRdanglingThrow); } if (buildAST) { pnode->AsParseNodeUni()->pnode1 = pnode1; if (pnode1) { this->CheckArguments(pnode->AsParseNodeUni()->pnode1); pnode->ichLim = pnode->AsParseNodeUni()->pnode1->ichLim; } } goto LNeedTerminator; } case tkDEBUGGER: if (buildAST) { pnode = CreateNodeForOpT(); } this->GetScanner()->Scan(); goto LNeedTerminator; case tkIMPORT: pnode = ParseImport(); goto LNeedTerminator; case tkEXPORT: { if (!(m_grfscr & fscrIsModuleCode)) { goto LDefaultToken; } bool needTerminator = false; pnode = ParseExportDeclaration(&needTerminator); if (needTerminator) { goto LNeedTerminator; } else { break; } } LDefaultToken: default: { // First check for a label via lookahead. If not found, // rewind and reparse as expression statement. if (m_token.tk == tkID) { RestorePoint idStart; this->GetScanner()->Capture(&idStart); IdentPtr pidInner = m_token.GetIdentifier(this->GetHashTbl()); this->GetScanner()->Scan(); if (m_token.tk == tkColon) { // We have a label. if (LabelExists(pidInner, pLabelIdList)) { Error(ERRbadLabel); } LabelId* pLabelId = CreateLabelId(pidInner); pLabelId->next = pLabelIdList; pLabelIdList = pLabelId; this->GetScanner()->Scan(); labelledStatement = true; goto LRestart; } // No label, rewind back to the tkID and parse an expression this->GetScanner()->SeekTo(idStart); } // Must be an expression statement. pnode = ParseExpr(); if (m_hasDeferredShorthandInitError) { Error(ERRnoColon); } if (buildAST) { expressionStmt = true; AnalysisAssert(pnode); pnode->isUsed = false; } } LNeedTerminator: // Need a semicolon, new-line, } or end-of-file. // We digest a semicolon if it's there. switch (m_token.tk) { case tkSColon: this->GetScanner()->Scan(); if (pnode != nullptr) pnode->grfpn |= PNodeFlags::fpnExplicitSemicolon; break; case tkEOF: case tkRCurly: if (pnode != nullptr) pnode->grfpn |= PNodeFlags::fpnAutomaticSemicolon; break; default: if (!this->GetScanner()->FHadNewLine()) { Error(ERRnoSemic); } else { if (pnode != nullptr) pnode->grfpn |= PNodeFlags::fpnAutomaticSemicolon; } break; } break; } if (m_hasDeferredShorthandInitError) { Error(ERRnoColon); } if (buildAST) { // All non expression statements excluded from the "this.x" optimization // Another check while parsing expressions if (!expressionStmt) { if (m_currentNodeFunc) { m_currentNodeFunc->SetHasNonThisStmt(); } else if (m_currentNodeProg) { m_currentNodeProg->SetHasNonThisStmt(); } } #if EXCEPTION_RECOVERY // close the try/catch block if (Js::Configuration::Global.flags.SwallowExceptions) { // pop the try block and fill in the body PopStmt(&stmtTryBlock); pTryBlock->pnodeStmt = pnode; PopStmt(&stmtTry); if (pnode != nullptr) { pTry->ichLim = pnode->ichLim; } pTry->pnodeBody = pTryBlock; // create a catch block with an empty body StmtNest stmtCatch; ParseNodeCatch * pCatch; pCatch = CreateNodeForOpT(); PushStmt(&stmtCatch, pCatch, knopCatch, nullptr); pCatch->pnodeBody = nullptr; if (pnode != nullptr) { pCatch->ichLim = pnode->ichLim; } pCatch->grfnop = 0; pCatch->pnodeNext = nullptr; // create a fake name for the catch var. const WCHAR *uniqueNameStr = _u("__ehobj"); IdentPtr uniqueName = this->GetHashTbl()->PidHashNameLen(uniqueNameStr, static_cast(wcslen(uniqueNameStr))); pCatch->SetParam(CreateNameNode(uniqueName)); // Add this catch to the current list. We don't bother adjusting the catch and function expression // lists here because the catch is just an empty statement. if (m_ppnodeExprScope) { Assert(*m_ppnodeExprScope == nullptr); *m_ppnodeExprScope = pCatch; m_ppnodeExprScope = &pCatch->pnodeNext; } else { Assert(m_ppnodeScope); Assert(*m_ppnodeScope == nullptr); *m_ppnodeScope = pCatch; m_ppnodeScope = &pCatch->pnodeNext; } pCatch->pnodeScopes = nullptr; PopStmt(&stmtCatch); // fill in and pop the try-catch pParentTryCatch->pnodeTry = pTry; pParentTryCatch->pnodeCatch = pCatch; PopStmt(&stmtTryCatch); PopStmt(&stmtTryCatchBlock); // replace the node that's being returned pParentTryCatchBlock->pnodeStmt = pParentTryCatch; pnode = pParentTryCatchBlock; } #endif // EXCEPTION_RECOVERY } return pnode; } BOOL Parser::TokIsForInOrForOf() { return m_token.tk == tkIN || (m_token.tk == tkID && m_token.GetIdentifier(this->GetHashTbl()) == wellKnownPropertyPids.of); } /*************************************************************************** Parse a sequence of statements. ***************************************************************************/ template void Parser::ParseStmtList(ParseNodePtr *ppnodeList, ParseNodePtr **pppnodeLast, StrictModeEnvironment smEnvironment, const bool isSourceElementList, bool* strictModeOn) { BOOL doneDirectives = !isSourceElementList; // directives may only exist in a SourceElementList, not a StatementList BOOL seenDirectiveContainingOctal = false; // Have we seen an octal directive before a use strict directive? BOOL old_UseStrictMode = m_fUseStrictMode; ParseNodePtr pnodeStmt; ParseNodePtr *lastNodeRef = nullptr; if (buildAST) { Assert(ppnodeList); *ppnodeList = nullptr; } if (CONFIG_FLAG(ForceStrictMode)) { m_fUseStrictMode = TRUE; } for (;;) { switch (m_token.tk) { case tkCASE: case tkDEFAULT: case tkRCurly: case tkEOF: if (buildAST && nullptr != pppnodeLast) { *pppnodeLast = lastNodeRef; } if (!buildAST) { m_fUseStrictMode = old_UseStrictMode; } return; } if (doneDirectives == FALSE) { bool isOctalInString = false; bool isUseStrictDirective = false; bool isUseAsmDirective = false; if (smEnvironment != SM_NotUsed && CheckForDirective(&isUseStrictDirective, &isUseAsmDirective, &isOctalInString)) { // Ignore "use asm" statement when not building the AST isUseAsmDirective &= buildAST; if (isUseStrictDirective) { // Functions with non-simple parameter list cannot be made strict mode if (GetCurrentFunctionNode()->HasNonSimpleParameterList()) { Error(ERRNonSimpleParamListInStrictMode); } if (seenDirectiveContainingOctal) { // Directives seen before a "use strict" cannot contain an octal. Error(ERRES5NoOctal); } if (!buildAST) { // Turning on strict mode in deferred code. m_fUseStrictMode = TRUE; if (!m_inDeferredNestedFunc) { // Top-level deferred function, so there's a parse node Assert(m_currentNodeFunc != nullptr); m_currentNodeFunc->SetStrictMode(); } else if (strictModeOn) { // This turns on strict mode in a deferred function, we need to go back // and re-check duplicated formals. *strictModeOn = true; } } else { if (smEnvironment == SM_OnGlobalCode) { // Turning on strict mode at the top level m_fUseStrictMode = TRUE; } else { // i.e. smEnvironment == SM_OnFunctionCode Assert(m_currentNodeFunc != nullptr); m_currentNodeFunc->SetStrictMode(); } } } else if (isUseAsmDirective) { if (smEnvironment != SM_OnGlobalCode) //Top level use asm doesn't mean anything. { // i.e. smEnvironment == SM_OnFunctionCode Assert(m_currentNodeFunc != nullptr); m_currentNodeFunc->SetAsmjsMode(); m_currentNodeFunc->SetCanBeDeferred(false); m_InAsmMode = true; CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, AsmJSFunction, m_scriptContext); } } else if (isOctalInString) { seenDirectiveContainingOctal = TRUE; } } else { // The first time we see anything other than a directive we can have no more directives. doneDirectives = TRUE; } } if (nullptr != (pnodeStmt = ParseStatement())) { if (buildAST) { AddToNodeList(ppnodeList, &lastNodeRef, pnodeStmt); } } } } template void Parser::FinishFunctionsInScope(ParseNodePtr pnodeScopeList, Fn fn) { Scope * scope; Scope * origCurrentScope = this->m_currentScope; ParseNodePtr pnodeScope; ParseNodeBlock * pnodeBlock; for (pnodeScope = pnodeScopeList; pnodeScope;) { switch (pnodeScope->nop) { case knopBlock: { ParseNodeBlock * pnodeBlockScope = pnodeScope->AsParseNodeBlock(); m_nextBlockId = pnodeBlockScope->blockId + 1; PushBlockInfo(pnodeBlockScope); scope = pnodeBlockScope->scope; if (scope && scope != origCurrentScope) { PushScope(scope); } FinishFunctionsInScope(pnodeBlockScope->pnodeScopes, fn); if (scope && scope != origCurrentScope) { BindPidRefs(GetCurrentBlockInfo(), m_nextBlockId - 1); PopScope(scope); } PopBlockInfo(); pnodeScope = pnodeBlockScope->pnodeNext; break; } case knopFncDecl: fn(pnodeScope->AsParseNodeFnc()); pnodeScope = pnodeScope->AsParseNodeFnc()->pnodeNext; break; case knopCatch: scope = pnodeScope->AsParseNodeCatch()->scope; if (scope) { PushScope(scope); } pnodeBlock = CreateBlockNode(PnodeBlockType::Regular); pnodeBlock->scope = scope; PushBlockInfo(pnodeBlock); FinishFunctionsInScope(pnodeScope->AsParseNodeCatch()->pnodeScopes, fn); if (scope) { BindPidRefs(GetCurrentBlockInfo(), m_nextBlockId - 1); PopScope(scope); } PopBlockInfo(); pnodeScope = pnodeScope->AsParseNodeCatch()->pnodeNext; break; case knopWith: PushBlockInfo(CreateBlockNode()); PushDynamicBlock(); FinishFunctionsInScope(pnodeScope->AsParseNodeWith()->pnodeScopes, fn); PopBlockInfo(); pnodeScope = pnodeScope->AsParseNodeWith()->pnodeNext; break; default: AssertMsg(false, "Unexpected node with scope list"); return; } } } // Scripts above this size (minus string literals and comments) will have parsing of // function bodies deferred. ULONG Parser::GetDeferralThreshold(bool isProfileLoaded) { #ifdef ENABLE_DEBUG_CONFIG_OPTIONS if (CONFIG_FLAG(ForceDeferParse) || PHASE_FORCE1(Js::DeferParsePhase) || Js::Configuration::Global.flags.IsEnabled(Js::ForceUndoDeferFlag)) { return 0; } else if (Js::Configuration::Global.flags.IsEnabled(Js::DeferParseFlag)) { return Js::Configuration::Global.flags.DeferParse; } else #endif { if (isProfileLoaded) { return DEFAULT_CONFIG_ProfileBasedDeferParseThreshold; } return DEFAULT_CONFIG_DeferParseThreshold; } } void Parser::FinishDeferredFunction(ParseNodeBlock * pnodeScopeList) { uint saveNextBlockId = m_nextBlockId; m_nextBlockId = pnodeScopeList->blockId + 1; FinishFunctionsInScope(pnodeScopeList, [this](ParseNodeFnc * pnodeFnc) { Assert(pnodeFnc->nop == knopFncDecl); // Non-simple params (such as default) require a good amount of logic to put vars on appropriate scopes. ParseFncDecl handles it // properly (both on defer and non-defer case). This is to avoid write duplicated logic here as well. Function with non-simple-param // will remain deferred until they are called. if (pnodeFnc->pnodeBody == nullptr && !pnodeFnc->HasNonSimpleParameterList()) { // Go back and generate an AST for this function. JS_ETW_INTERNAL(EventWriteJSCRIPT_PARSE_FUNC(this->GetScriptContext(), pnodeFnc->functionId, /*Undefer*/TRUE)); ParseNodeFnc * pnodeFncSave = this->m_currentNodeFunc; this->m_currentNodeFunc = pnodeFnc; ParseNodeBlock * pnodeFncExprBlock = nullptr; ParseNodePtr pnodeName = pnodeFnc->pnodeName; if (pnodeName) { Assert(pnodeName->nop == knopVarDecl); ParseNodeVar * pnodeVarName = pnodeName->AsParseNodeVar(); Assert(pnodeVarName->pnodeNext == nullptr); if (!pnodeFnc->IsDeclaration()) { // Set up the named function expression symbol so references inside the function can be bound. pnodeFncExprBlock = this->StartParseBlock(PnodeBlockType::Function, ScopeType_FuncExpr); PidRefStack *ref = this->PushPidRef(pnodeVarName->pid); pnodeVarName->symRef = ref->GetSymRef(); ref->SetSym(pnodeVarName->sym); Scope *fncExprScope = pnodeFncExprBlock->scope; fncExprScope->AddNewSymbol(pnodeVarName->sym); pnodeFnc->scope = fncExprScope; } } ParseNodeBlock * pnodeBlock = this->StartParseBlock(PnodeBlockType::Parameter, ScopeType_Parameter); pnodeFnc->pnodeScopes = pnodeBlock; m_ppnodeScope = &pnodeBlock->pnodeScopes; pnodeBlock->pnodeStmt = pnodeFnc; ParseNodePtr * varNodesList = &pnodeFnc->pnodeVars; ParseNodeVar * argNode = nullptr; if (!pnodeFnc->IsModule() && !pnodeFnc->IsLambda() && !(pnodeFnc->grfpn & PNodeFlags::fpnArguments_overriddenInParam)) { ParseNodePtr *const ppnodeVarSave = m_ppnodeVar; m_ppnodeVar = &pnodeFnc->pnodeVars; argNode = this->AddArgumentsNodeToVars(pnodeFnc); varNodesList = m_ppnodeVar; m_ppnodeVar = ppnodeVarSave; } // Add the args to the scope, since we won't re-parse those. Scope *scope = pnodeBlock->scope; uint blockId = GetCurrentBlock()->blockId; uint funcId = GetCurrentFunctionNode()->functionId; auto addArgsToScope = [&](ParseNodePtr pnodeArg) { if (pnodeArg->IsVarLetOrConst()) { ParseNodeVar * pnodeVarArg = pnodeArg->AsParseNodeVar(); PidRefStack *ref = this->FindOrAddPidRef(pnodeVarArg->pid, blockId, funcId); pnodeVarArg->symRef = ref->GetSymRef(); if (ref->GetSym() != nullptr) { // Duplicate parameter in a configuration that allows them. // The symbol is already in the scope, just point it to the right declaration. Assert(ref->GetSym() == pnodeVarArg->sym); ref->GetSym()->SetDecl(pnodeVarArg); } else { ref->SetSym(pnodeArg->AsParseNodeVar()->sym); scope->AddNewSymbol(pnodeVarArg->sym); } } }; MapFormals(pnodeFnc, addArgsToScope); MapFormalsFromPattern(pnodeFnc, addArgsToScope); ParseNodeBlock * pnodeInnerBlock = this->StartParseBlock(PnodeBlockType::Function, ScopeType_FunctionBody); pnodeFnc->pnodeBodyScope = pnodeInnerBlock; // Set the parameter block's child to the function body block. *m_ppnodeScope = pnodeInnerBlock; ParseNodePtr *ppnodeScopeSave = nullptr; ParseNodePtr *ppnodeExprScopeSave = nullptr; ppnodeScopeSave = m_ppnodeScope; // This synthetic block scope will contain all the nested scopes. m_ppnodeScope = &pnodeInnerBlock->pnodeScopes; pnodeInnerBlock->pnodeStmt = pnodeFnc; // Keep nested function declarations and expressions in the same list at function scope. // (Indicate this by nulling out the current function expressions list.) ppnodeExprScopeSave = m_ppnodeExprScope; m_ppnodeExprScope = nullptr; // Shouldn't be any temps in the arg list. Assert(*m_ppnodeVar == nullptr); // Start the var list. m_ppnodeVar = varNodesList; if (scope != nullptr) { Assert(pnodeFnc->IsBodyAndParamScopeMerged()); blockId = GetCurrentBlock()->blockId; funcId = GetCurrentFunctionNode()->functionId; scope->ForEachSymbol([this, blockId, funcId](Symbol* paramSym) { PidRefStack* ref = this->FindOrAddPidRef(paramSym->GetPid(), blockId, funcId); ref->SetSym(paramSym); }); } Assert(m_currentNodeNonLambdaFunc == nullptr); m_currentNodeNonLambdaFunc = pnodeFnc; this->FinishFncNode(pnodeFnc); Assert(pnodeFnc == m_currentNodeNonLambdaFunc); m_currentNodeNonLambdaFunc = nullptr; m_ppnodeExprScope = ppnodeExprScopeSave; Assert(m_ppnodeScope); Assert(nullptr == *m_ppnodeScope); m_ppnodeScope = ppnodeScopeSave; this->FinishParseBlock(pnodeInnerBlock); if (!pnodeFnc->IsModule() && (m_token.tk == tkLCurly || !pnodeFnc->IsLambda())) { UpdateArgumentsNode(pnodeFnc, argNode); } CreateSpecialSymbolDeclarations(pnodeFnc); this->FinishParseBlock(pnodeBlock); if (pnodeFncExprBlock) { this->FinishParseBlock(pnodeFncExprBlock); } this->m_currentNodeFunc = pnodeFncSave; } }); m_nextBlockId = saveNextBlockId; } void Parser::InitPids() { wellKnownPropertyPids.arguments = this->GetHashTbl()->PidHashNameLen(g_ssym_arguments.sz, g_ssym_arguments.cch); wellKnownPropertyPids.async = this->GetHashTbl()->PidHashNameLen(g_ssym_async.sz, g_ssym_async.cch); wellKnownPropertyPids.eval = this->GetHashTbl()->PidHashNameLen(g_ssym_eval.sz, g_ssym_eval.cch); wellKnownPropertyPids.get = this->GetHashTbl()->PidHashNameLen(g_ssym_get.sz, g_ssym_get.cch); wellKnownPropertyPids.set = this->GetHashTbl()->PidHashNameLen(g_ssym_set.sz, g_ssym_set.cch); wellKnownPropertyPids.let = this->GetHashTbl()->PidHashNameLen(g_ssym_let.sz, g_ssym_let.cch); wellKnownPropertyPids.constructor = this->GetHashTbl()->PidHashNameLen(g_ssym_constructor.sz, g_ssym_constructor.cch); wellKnownPropertyPids.prototype = this->GetHashTbl()->PidHashNameLen(g_ssym_prototype.sz, g_ssym_prototype.cch); wellKnownPropertyPids.__proto__ = this->GetHashTbl()->PidHashNameLen(_u("__proto__"), sizeof("__proto__") - 1); wellKnownPropertyPids.of = this->GetHashTbl()->PidHashNameLen(_u("of"), sizeof("of") - 1); wellKnownPropertyPids.target = this->GetHashTbl()->PidHashNameLen(_u("target"), sizeof("target") - 1); wellKnownPropertyPids.as = this->GetHashTbl()->PidHashNameLen(_u("as"), sizeof("as") - 1); wellKnownPropertyPids.from = this->GetHashTbl()->PidHashNameLen(_u("from"), sizeof("from") - 1); wellKnownPropertyPids._default = this->GetHashTbl()->PidHashNameLen(_u("default"), sizeof("default") - 1); wellKnownPropertyPids._starDefaultStar = this->GetHashTbl()->PidHashNameLen(_u("*default*"), sizeof("*default*") - 1); wellKnownPropertyPids._star = this->GetHashTbl()->PidHashNameLen(_u("*"), sizeof("*") - 1); wellKnownPropertyPids._this = this->GetHashTbl()->PidHashNameLen(_u("*this*"), sizeof("*this*") - 1); wellKnownPropertyPids._newTarget = this->GetHashTbl()->PidHashNameLen(_u("*new.target*"), sizeof("*new.target*") - 1); wellKnownPropertyPids._super = this->GetHashTbl()->PidHashNameLen(_u("*super*"), sizeof("*super*") - 1); wellKnownPropertyPids._superConstructor = this->GetHashTbl()->PidHashNameLen(_u("*superconstructor*"), sizeof("*superconstructor*") - 1); } void Parser::RestoreScopeInfo(Js::ScopeInfo * scopeInfo) { if (!scopeInfo) { return; } PROBE_STACK_NO_DISPOSE(m_scriptContext, Js::Constants::MinStackByteCodeVisitor); RestoreScopeInfo(scopeInfo->GetParentScopeInfo()); // Recursively restore outer func scope info scopeInfo->SetScopeId(m_nextBlockId); ParseNodeBlock * pnodeScope = nullptr; ScopeType scopeType = scopeInfo->GetScopeType(); PnodeBlockType blockType; switch (scopeType) { case ScopeType_With: PushDynamicBlock(); // fall through case ScopeType_Block: case ScopeType_Catch: case ScopeType_CatchParamPattern: case ScopeType_GlobalEvalBlock: blockType = PnodeBlockType::Regular; break; case ScopeType_FunctionBody: case ScopeType_FuncExpr: blockType = PnodeBlockType::Function; break; case ScopeType_Parameter: blockType = PnodeBlockType::Parameter; break; default: Assert(0); return; } pnodeScope = StartParseBlockWithCapacity(blockType, scopeType, scopeInfo->GetSymbolCount()); Scope *scope = pnodeScope->scope; scope->SetScopeInfo(scopeInfo); scopeInfo->ExtractScopeInfo(this, /*nullptr, nullptr,*/ scope); } void Parser::FinishScopeInfo(Js::ScopeInfo * scopeInfo) { PROBE_STACK_NO_DISPOSE(m_scriptContext, Js::Constants::MinStackByteCodeVisitor); for (; scopeInfo != nullptr; scopeInfo = scopeInfo->GetParentScopeInfo()) { int scopeId = scopeInfo->GetScopeId(); scopeInfo->GetScope()->ForEachSymbol([this, scopeId](Symbol *sym) { this->BindPidRefsInScope(sym->GetPid(), sym, scopeId); }); PopScope(scopeInfo->GetScope()); PopStmt(&m_currentBlockInfo->pstmt); PopBlockInfo(); } } /*************************************************************************** Parse the code. ***************************************************************************/ ParseNodeProg * Parser::Parse(LPCUTF8 pszSrc, size_t offset, size_t length, charcount_t charOffset, bool isUtf8, ULONG grfscr, ULONG lineNumber, Js::LocalFunctionId * nextFunctionId, CompileScriptException *pse) { ParseNodeProg * pnodeProg; ParseNodePtr *lastNodeRef = nullptr; m_nextBlockId = 0; bool isDeferred = (grfscr & fscrDeferredFnc) != 0; bool isModuleSource = (grfscr & fscrIsModuleCode) != 0; bool isGlobalCode = (grfscr & fscrGlobalCode) != 0; if (this->m_scriptContext->IsScriptContextInDebugMode() #ifdef ENABLE_PREJIT || Js::Configuration::Global.flags.Prejit #endif || ((grfscr & fscrNoDeferParse) != 0) ) { // Don't do deferred parsing if debugger is attached or feature is disabled // by command-line switch. grfscr &= ~fscrWillDeferFncParse; } else if (!isGlobalCode && ( PHASE_OFF1(Js::Phase::DeferEventHandlersPhase) || this->m_scriptContext->IsScriptContextInSourceRundownOrDebugMode() ) ) { // Don't defer event handlers in debug/rundown mode, because we need to register the document, // so we need to create a full FunctionBody for the script body. grfscr &= ~fscrWillDeferFncParse; } m_grfscr = grfscr; m_length = length; m_originalLength = length; m_nextFunctionId = nextFunctionId; if (m_parseType != ParseType_Deferred) { JS_ETW(EventWriteJSCRIPT_PARSE_METHOD_START(m_sourceContextInfo->dwHostSourceContext, GetScriptContext(), *m_nextFunctionId, 0, m_parseType, Js::Constants::GlobalFunction)); OUTPUT_TRACE(Js::DeferParsePhase, _u("Parsing function (%s) : %s (%d)\n"), GetParseType(), Js::Constants::GlobalFunction, *m_nextFunctionId); } // Give the scanner the source and get the first token this->GetScanner()->SetText(pszSrc, offset, length, charOffset, isUtf8, grfscr, lineNumber); this->GetScanner()->Scan(); // Make the main 'knopProg' node int32 initSize = 0; m_pCurrentAstSize = &initSize; pnodeProg = CreateProgNode(isModuleSource, lineNumber); if (!isDeferred || (isDeferred && isGlobalCode)) { // In the deferred case, if the global function is deferred parse (which is in no-refresh case), // we will re-use the same function body, so start with the correct functionId. pnodeProg->functionId = (*m_nextFunctionId)++; } if (isModuleSource) { Assert(m_scriptContext->GetConfig()->IsES6ModuleEnabled()); pnodeProg->AsParseNodeModule()->localExportEntries = nullptr; pnodeProg->AsParseNodeModule()->indirectExportEntries = nullptr; pnodeProg->AsParseNodeModule()->starExportEntries = nullptr; pnodeProg->AsParseNodeModule()->importEntries = nullptr; pnodeProg->AsParseNodeModule()->requestedModules = nullptr; } m_pCurrentAstSize = &(pnodeProg->astSize); // initialize parsing variables m_currentNodeFunc = nullptr; m_currentNodeDeferredFunc = nullptr; m_currentNodeProg = pnodeProg; m_cactIdentToNodeLookup = 1; m_pnestedCount = &pnodeProg->nestedCount; m_inDeferredNestedFunc = false; m_ppnodeVar = &pnodeProg->pnodeVars; SetCurrentStatement(nullptr); AssertMsg(m_pstmtCur == nullptr, "Statement stack should be empty when we start parse global code"); // Create block for const's and let's ParseNodeBlock * pnodeGlobalBlock = StartParseBlock(PnodeBlockType::Global, ScopeType_Global); pnodeProg->scope = pnodeGlobalBlock->scope; ParseNodeBlock * pnodeGlobalEvalBlock = nullptr; // Don't track function expressions separately from declarations at global scope. m_ppnodeExprScope = nullptr; // This synthetic block scope will contain all the nested scopes. pnodeProg->pnodeScopes = pnodeGlobalBlock; m_ppnodeScope = &pnodeGlobalBlock->pnodeScopes; if ((this->m_grfscr & fscrEvalCode) && !(this->m_functionBody && this->m_functionBody->GetScopeInfo())) { pnodeGlobalEvalBlock = StartParseBlock(PnodeBlockType::Regular, ScopeType_GlobalEvalBlock); pnodeProg->pnodeScopes = pnodeGlobalEvalBlock; m_ppnodeScope = &pnodeGlobalEvalBlock->pnodeScopes; } Js::ScopeInfo *scopeInfo = nullptr; if (m_parseType == ParseType_Deferred && m_functionBody) { // this->m_functionBody can be cleared during parsing, but we need access to the scope info later. scopeInfo = m_functionBody->GetScopeInfo(); if (scopeInfo) { // Create an enclosing function context. m_currentNodeFunc = CreateNodeForOpT(); m_currentNodeFunc->functionId = m_functionBody->GetLocalFunctionId(); m_currentNodeFunc->nestedCount = m_functionBody->GetNestedCount(); m_currentNodeFunc->SetStrictMode(!!this->m_fUseStrictMode); this->RestoreScopeInfo(scopeInfo); m_currentNodeFunc->SetIsGenerator(scopeInfo->IsGeneratorFunctionBody()); m_currentNodeFunc->SetIsAsync(scopeInfo->IsAsyncFunctionBody()); } } // It's possible for the module global to be defer-parsed in debug scenarios. if (isModuleSource && (!isDeferred || (isDeferred && isGlobalCode))) { ParseNodePtr moduleFunction = GenerateModuleFunctionWrapper(); pnodeProg->pnodeBody = nullptr; AddToNodeList(&pnodeProg->pnodeBody, &lastNodeRef, moduleFunction); } else { if (isDeferred && !isGlobalCode) { // Defer parse for a single function should just parse that one function - there are no other statements. ushort flags = fFncNoFlgs; bool isAsync = false; bool isGenerator = false; bool isMethod = false; // The top-level deferred function body was defined by a function expression whose parsing was deferred. We are now // parsing it, so unset the flag so that any nested functions are parsed normally. This flag is only applicable the // first time we see it. // // Normally, deferred functions will be parsed in ParseStatement upon encountering the 'function' token. The first // token of the source code of the function may not be a 'function' token though, so we still need to reset this flag // for the first function we parse. This can happen in compat modes, for instance, for a function expression enclosed // in parentheses, where the legacy behavior was to include the parentheses in the function's source code. if (m_grfscr & fscrDeferredFncExpression) { m_grfscr &= ~fscrDeferredFncExpression; } else { flags |= fFncDeclaration; } if (m_grfscr & fscrDeferredFncIsMethod) { m_grfscr &= ~fscrDeferredFncIsMethod; isMethod = true; flags |= fFncNoName | fFncMethod; if (m_grfscr & fscrDeferredFncIsGenerator) { m_grfscr &= ~fscrDeferredFncIsGenerator; isGenerator = true; flags |= fFncGenerator; } if (m_token.tk == tkStar && m_scriptContext->GetConfig()->IsES6GeneratorsEnabled()) { Assert(isGenerator && !isMethod); this->GetScanner()->Scan(); } } if (m_grfscr & fscrDeferredFncIsAsync) { m_grfscr &= ~fscrDeferredFncIsAsync; isAsync = true; flags |= fFncAsync; } #if DBG if (isMethod && m_token.tk == tkID) { RestorePoint atPid; IdentPtr pidHint = m_token.GetIdentifier(this->GetHashTbl()); this->GetScanner()->Capture(&atPid); this->GetScanner()->Scan(); if ((pidHint == wellKnownPropertyPids.get || pidHint == wellKnownPropertyPids.set) && NextTokenIsPropertyNameStart()) { // Getter/setter // Skip the get/set keyword and continue normally AssertMsg(false, "We should not be re-parsing the get/set part of member accessor functions"); } else { // Not a getter/setter; rewind and treat the token as a name. this->GetScanner()->SeekTo(atPid); } } #endif // Ensure this isn't a computed name AssertMsg(!(m_token.tk == tkLBrack && isMethod), "Can't defer parse a computed name expression, we should have started after this"); if (!isMethod && (m_token.tk == tkID || m_token.tk == tkLParen)) { // If first token of the function is tkID or tkLParen, this is a lambda. flags |= fFncLambda; } ParseNode * pnodeFnc = ParseFncDeclCheckScope(flags); pnodeProg->pnodeBody = nullptr; AddToNodeList(&pnodeProg->pnodeBody, &lastNodeRef, pnodeFnc); // No need to update the cbStringMin property since no ParseableFunctionInfo will be created from this defer-parsed pnodeFnc } else { // Process a sequence of statements/declarations ParseStmtList( &pnodeProg->pnodeBody, &lastNodeRef, SM_OnGlobalCode, !(m_grfscr & fscrDeferredFncExpression) /* isSourceElementList */); } } if (m_parseType == ParseType_Deferred) { if (scopeInfo) { this->FinishScopeInfo(scopeInfo); } } pnodeProg->m_UsesArgumentsAtGlobal = m_UsesArgumentsAtGlobal; if (IsStrictMode()) { pnodeProg->SetStrictMode(); } #if DEBUG if (m_grfscr & fscrEnforceJSON && !IsJSONValid(pnodeProg->pnodeBody)) { Error(ERRsyntax); } #endif if (tkEOF != m_token.tk) Error(ERRsyntax); // Append an EndCode node. AddToNodeList(&pnodeProg->pnodeBody, &lastNodeRef, CreateNodeForOpT()); Assert(lastNodeRef); Assert(*lastNodeRef); Assert((*lastNodeRef)->nop == knopEndCode); (*lastNodeRef)->ichMin = 0; (*lastNodeRef)->ichLim = 0; // Get the extent of the code. pnodeProg->ichLim = this->GetScanner()->IchLimTok(); pnodeProg->cbLim = this->GetScanner()->IecpLimTok(); // Terminate the local list *m_ppnodeVar = nullptr; Assert(nullptr == *m_ppnodeScope); Assert(nullptr == pnodeProg->pnodeNext); #ifdef ENABLE_DEBUG_CONFIG_OPTIONS if (Js::Configuration::Global.flags.IsEnabled(Js::ForceUndoDeferFlag)) { m_stoppedDeferredParse = true; } #endif if (m_stoppedDeferredParse) { #if ENABLE_BACKGROUND_PARSING if (this->m_hasParallelJob) { BackgroundParser *bgp = static_cast(m_scriptContext->GetBackgroundParser()); Assert(bgp); this->WaitForBackgroundJobs(bgp, pse); } #endif // Do any remaining bindings of globals referenced in non-deferred functions. if (pnodeGlobalEvalBlock) { FinishParseBlock(pnodeGlobalEvalBlock); } FinishParseBlock(pnodeGlobalBlock); // Clear out references to undeclared identifiers. this->GetHashTbl()->VisitPids([&](IdentPtr pid) { pid->SetTopRef(nullptr); }); // Restore global scope and blockinfo stacks preparatory to reparsing deferred functions. PushScope(pnodeGlobalBlock->scope); BlockInfoStack *newBlockInfo = PushBlockInfo(pnodeGlobalBlock); PushStmt(&newBlockInfo->pstmt, pnodeGlobalBlock, knopBlock, nullptr); if (pnodeGlobalEvalBlock) { PushScope(pnodeGlobalEvalBlock->scope); newBlockInfo = PushBlockInfo(pnodeGlobalEvalBlock); PushStmt(&newBlockInfo->pstmt, pnodeGlobalEvalBlock, knopBlock, nullptr); } // Finally, see if there are any function bodies we now want to generate because we // decided to stop deferring. FinishDeferredFunction(pnodeProg->pnodeScopes); } if (pnodeGlobalEvalBlock) { FinishParseBlock(pnodeGlobalEvalBlock); } // Append block as body of pnodeProg FinishParseBlock(pnodeGlobalBlock); m_scriptContext->AddSourceSize(m_length); if (m_parseType != ParseType_Deferred) { JS_ETW(EventWriteJSCRIPT_PARSE_METHOD_STOP(m_sourceContextInfo->dwHostSourceContext, GetScriptContext(), pnodeProg->functionId, *m_pCurrentAstSize, false, Js::Constants::GlobalFunction)); } return pnodeProg; } bool Parser::CheckForDirective(bool* pIsUseStrict, bool *pIsUseAsm, bool* pIsOctalInString) { // A directive is a string constant followed by a statement terminating token if (m_token.tk != tkStrCon) return false; // Careful, need to check for octal before calling this->GetScanner()->Scan() // because Scan() clears the "had octal" flag on the scanner and // this->GetScanner()->Restore() does not restore this flag. if (pIsOctalInString != nullptr) { *pIsOctalInString = this->GetScanner()->IsOctOrLeadingZeroOnLastTKNumber(); } Ident* pidDirective = m_token.GetStr(); RestorePoint start; this->GetScanner()->Capture(&start); this->GetScanner()->Scan(); bool isDirective = true; switch (m_token.tk) { case tkSColon: case tkEOF: case tkLCurly: case tkRCurly: break; default: if (!this->GetScanner()->FHadNewLine()) { isDirective = false; } break; } if (isDirective) { if (pIsUseStrict != nullptr) { *pIsUseStrict = CheckStrictModeStrPid(pidDirective); } if (pIsUseAsm != nullptr) { *pIsUseAsm = CheckAsmjsModeStrPid(pidDirective); } } this->GetScanner()->SeekTo(start); return isDirective; } bool Parser::CheckStrictModeStrPid(IdentPtr pid) { #ifdef ENABLE_DEBUG_CONFIG_OPTIONS if (Js::Configuration::Global.flags.NoStrictMode) return false; #endif return pid != nullptr && pid->Cch() == 10 && !this->GetScanner()->IsEscapeOnLastTkStrCon() && wcsncmp(pid->Psz(), _u("use strict"), 10) == 0; } bool Parser::CheckAsmjsModeStrPid(IdentPtr pid) { #ifdef ASMJS_PLAT if (!CONFIG_FLAG(AsmJs)) { return false; } bool isAsmCandidate = (pid != nullptr && AutoSystemInfo::Data.SSE2Available() && pid->Cch() == 7 && !this->GetScanner()->IsEscapeOnLastTkStrCon() && wcsncmp(pid->Psz(), _u("use asm"), 10) == 0); #ifdef ENABLE_SCRIPT_DEBUGGING if (isAsmCandidate && m_scriptContext->IsScriptContextInDebugMode()) { // We would like to report this to debugger - they may choose to disable debugging. // TODO : localization of the string? m_scriptContext->RaiseMessageToDebugger(DEIT_ASMJS_IN_DEBUGGING, _u("AsmJs initialization error - AsmJs disabled due to script debugger"), m_sourceContextInfo && !m_sourceContextInfo->IsDynamic() ? m_sourceContextInfo->url : nullptr); return false; } #endif return isAsmCandidate && !(m_grfscr & fscrNoAsmJs); #else return false; #endif } HRESULT Parser::ParseUtf8Source(__out ParseNodeProg ** parseTree, LPCUTF8 pSrc, size_t length, ULONG grfsrc, CompileScriptException *pse, Js::LocalFunctionId * nextFunctionId, SourceContextInfo * sourceContextInfo) { m_functionBody = nullptr; m_parseType = ParseType_Upfront; return ParseSourceInternal(parseTree, pSrc, 0, length, 0, true, grfsrc, pse, nextFunctionId, 0, sourceContextInfo); } HRESULT Parser::ParseCesu8Source(__out ParseNodeProg ** parseTree, LPCUTF8 pSrc, size_t length, ULONG grfsrc, CompileScriptException *pse, Js::LocalFunctionId * nextFunctionId, SourceContextInfo * sourceContextInfo) { m_functionBody = nullptr; m_parseType = ParseType_Upfront; return ParseSourceInternal(parseTree, pSrc, 0, length, 0, false, grfsrc, pse, nextFunctionId, 0, sourceContextInfo); } #if ENABLE_BACKGROUND_PARSING void Parser::PrepareForBackgroundParse() { this->GetScanner()->PrepareForBackgroundParse(m_scriptContext); } void Parser::AddBackgroundParseItem(BackgroundParseItem *const item) { if (currBackgroundParseItem == nullptr) { backgroundParseItems = item; } else { currBackgroundParseItem->SetNext(item); } currBackgroundParseItem = item; } void Parser::AddFastScannedRegExpNode(ParseNodePtr const pnode) { Assert(!IsBackgroundParser()); Assert(m_doingFastScan); if (fastScannedRegExpNodes == nullptr) { fastScannedRegExpNodes = Anew(&m_nodeAllocator, NodeDList, &m_nodeAllocator); } fastScannedRegExpNodes->Append(pnode); } void Parser::AddBackgroundRegExpNode(ParseNodePtr const pnode) { Assert(IsBackgroundParser()); Assert(currBackgroundParseItem != nullptr); currBackgroundParseItem->AddRegExpNode(pnode, &m_nodeAllocator); } HRESULT Parser::ParseFunctionInBackground(ParseNodeFnc * pnodeFnc, ParseContext *parseContext, bool topLevelDeferred, CompileScriptException *pse) { m_functionBody = nullptr; m_parseType = ParseType_Upfront; HRESULT hr = S_OK; SmartFPUControl smartFpuControl; uint nextFunctionId = pnodeFnc->functionId + 1; this->RestoreContext(parseContext); m_nextFunctionId = &nextFunctionId; m_deferringAST = topLevelDeferred; m_inDeferredNestedFunc = false; m_scopeCountNoAst = 0; SetCurrentStatement(nullptr); pnodeFnc->pnodeVars = nullptr; pnodeFnc->pnodeParams = nullptr; pnodeFnc->pnodeBody = nullptr; pnodeFnc->nestedCount = 0; ParseNodeFnc * pnodeParentFnc = GetCurrentFunctionNode(); m_currentNodeFunc = pnodeFnc; m_currentNodeDeferredFunc = nullptr; m_ppnodeScope = nullptr; m_ppnodeExprScope = nullptr; m_pnestedCount = &pnodeFnc->nestedCount; m_pCurrentAstSize = &pnodeFnc->astSize; ParseNodeBlock * pnodeBlock = StartParseBlock(PnodeBlockType::Function, ScopeType_FunctionBody); pnodeFnc->pnodeScopes = pnodeBlock; m_ppnodeScope = &pnodeBlock->pnodeScopes; uint uDeferSave = m_grfscr & (fscrCanDeferFncParse | fscrWillDeferFncParse); try { this->GetScanner()->Scan(); m_ppnodeVar = &pnodeFnc->pnodeParams; this->ParseFncFormals(pnodeFnc, pnodeParentFnc, fFncNoFlgs); if (m_token.tk == tkRParen) { this->GetScanner()->Scan(); } ChkCurTok(tkLCurly, ERRnoLcurly); m_ppnodeVar = &pnodeFnc->pnodeVars; // Put the scanner into "no hashing" mode. BYTE deferFlags = this->GetScanner()->SetDeferredParse(topLevelDeferred); // Process a sequence of statements/declarations if (topLevelDeferred) { ParseStmtList(nullptr, nullptr, SM_DeferredParse, true); } else { ParseNodePtr *lastNodeRef = nullptr; ParseStmtList(&pnodeFnc->pnodeBody, &lastNodeRef, SM_OnFunctionCode, true); AddArgumentsNodeToVars(pnodeFnc); // Append an EndCode node. AddToNodeList(&pnodeFnc->pnodeBody, &lastNodeRef, CreateNodeForOpT()); } // Restore the scanner's default hashing mode. this->GetScanner()->SetDeferredParseFlags(deferFlags); #if DBG pnodeFnc->deferredParseNextFunctionId = *this->m_nextFunctionId; #endif this->m_deferringAST = FALSE; // Append block as body of pnodeProg FinishParseBlock(pnodeBlock); } catch (ParseExceptionObject& e) { hr = e.GetError(); } if (FAILED(hr)) { hr = pse->ProcessError(this->GetScanner(), hr, nullptr); } if (IsStrictMode()) { pnodeFnc->SetStrictMode(); } if (topLevelDeferred) { pnodeFnc->pnodeVars = nullptr; } m_grfscr |= uDeferSave; Assert(nullptr == *m_ppnodeScope); return hr; } #endif HRESULT Parser::ParseSourceWithOffset(__out ParseNodeProg ** parseTree, LPCUTF8 pSrc, size_t offset, size_t cbLength, charcount_t cchOffset, bool isCesu8, ULONG grfscr, CompileScriptException *pse, Js::LocalFunctionId * nextFunctionId, ULONG lineNumber, SourceContextInfo * sourceContextInfo, Js::ParseableFunctionInfo* functionInfo) { m_functionBody = functionInfo; if (m_functionBody) { m_currDeferredStub = m_functionBody->GetDeferredStubs(); m_currDeferredStubCount = m_currDeferredStub != nullptr ? m_functionBody->GetNestedCount() : 0; m_InAsmMode = grfscr & fscrNoAsmJs ? false : m_functionBody->GetIsAsmjsMode(); } m_deferAsmJs = !m_InAsmMode; m_parseType = ParseType_Deferred; return ParseSourceInternal(parseTree, pSrc, offset, cbLength, cchOffset, !isCesu8, grfscr, pse, nextFunctionId, lineNumber, sourceContextInfo); } bool Parser::IsStrictMode() const { return (m_fUseStrictMode || (m_currentNodeFunc != nullptr && m_currentNodeFunc->GetStrictMode())); } BOOL Parser::ExpectingExternalSource() { return m_fExpectExternalSource; } Symbol *ParseNodeFnc::GetFuncSymbol() { if (pnodeName) { Assert(pnodeName->nop == knopVarDecl); return pnodeName->sym; } return nullptr; } void ParseNodeFnc::SetFuncSymbol(Symbol *sym) { Assert(pnodeName); Assert(pnodeName->nop == knopVarDecl); pnodeName->sym = sym; } ParseNodePtr ParseNodeFnc::GetParamScope() const { if (this->pnodeScopes == nullptr) { return nullptr; } Assert(this->pnodeScopes->nop == knopBlock && this->pnodeScopes->pnodeNext == nullptr); return this->pnodeScopes->pnodeScopes; } ParseNodePtr ParseNodeFnc::GetBodyScope() const { if (this->pnodeBodyScope == nullptr) { return nullptr; } Assert(this->pnodeBodyScope->nop == knopBlock && this->pnodeBodyScope->pnodeNext == nullptr); return this->pnodeBodyScope->pnodeScopes; } bool ParseNodeBlock::HasBlockScopedContent() const { // A block has its own content if a let, const, or function is declared there. if (this->pnodeLexVars != nullptr || this->blockType == Parameter) { return true; } // The enclosing scopes can contain functions and other things, so walk the list // looking specifically for functions. for (ParseNodePtr pnode = this->pnodeScopes; pnode;) { switch (pnode->nop) { case knopFncDecl: return true; case knopBlock: pnode = pnode->AsParseNodeBlock()->pnodeNext; break; case knopCatch: pnode = pnode->AsParseNodeCatch()->pnodeNext; break; case knopWith: pnode = pnode->AsParseNodeWith()->pnodeNext; break; default: Assert(UNREACHED); return true; } } return false; } class ByteCodeGenerator; // Copy AST; this works mostly on expressions for now ParseNode* Parser::CopyPnode(ParseNode *pnode) { if (pnode == NULL) return NULL; switch (pnode->nop) { //PTNODE(knopName , "name" ,None ,Pid ,fnopLeaf) case knopName: { ParseNodeName * nameNode = CreateNameNode(pnode->AsParseNodeName()->pid); nameNode->ichMin = pnode->ichMin; nameNode->ichLim = pnode->ichLim; nameNode->sym = pnode->AsParseNodeName()->sym; return nameNode; } //PTNODE(knopInt , "int const" ,None ,Int ,fnopLeaf|fnopConst) case knopInt: return pnode; //PTNODE(knopFlt , "flt const" ,None ,Flt ,fnopLeaf|fnopConst) case knopFlt: return pnode; //PTNODE(knopStr , "str const" ,None ,Pid ,fnopLeaf|fnopConst) case knopStr: return pnode; //PTNODE(knopRegExp , "reg expr" ,None ,Pid ,fnopLeaf|fnopConst) case knopRegExp: return pnode; break; //PTNODE(knopNull , "null" ,Null ,None ,fnopLeaf) case knopNull: return pnode; //PTNODE(knopFalse , "false" ,False ,None ,fnopLeaf) case knopFalse: { ParseNode* ret = CreateNodeForOpT(pnode->ichMin, pnode->ichLim); ret->location = pnode->location; return ret; } //PTNODE(knopTrue , "true" ,True ,None ,fnopLeaf) case knopTrue: { ParseNode* ret = CreateNodeForOpT(pnode->ichMin, pnode->ichLim); ret->location = pnode->location; return ret; } //PTNODE(knopEmpty , "empty" ,Empty ,None ,fnopLeaf) case knopEmpty: return CreateNodeForOpT(pnode->ichMin, pnode->ichLim); // Unary operators. //PTNODE(knopNot , "~" ,BitNot ,Uni ,fnopUni) //PTNODE(knopNeg , "unary -" ,Neg ,Uni ,fnopUni) //PTNODE(knopPos , "unary +" ,Pos ,Uni ,fnopUni) //PTNODE(knopLogNot , "!" ,LogNot ,Uni ,fnopUni) //PTNODE(knopEllipsis , "..." ,Spread ,Uni , fnopUni) //PTNODE(knopDecPost , "-- post" ,Dec ,Uni ,fnopUni|fnopAsg) //PTNODE(knopIncPre , "++ pre" ,Inc ,Uni ,fnopUni|fnopAsg) //PTNODE(knopDecPre , "-- pre" ,Dec ,Uni ,fnopUni|fnopAsg) //PTNODE(knopTypeof , "typeof" ,None ,Uni ,fnopUni) //PTNODE(knopVoid , "void" ,Void ,Uni ,fnopUni) //PTNODE(knopDelete , "delete" ,None ,Uni ,fnopUni) case knopNot: case knopNeg: case knopPos: case knopLogNot: case knopEllipsis: case knopIncPost: case knopDecPost: case knopIncPre: case knopDecPre: case knopTypeof: case knopVoid: case knopDelete: return CreateUniNode(pnode->nop, CopyPnode(pnode->AsParseNodeUni()->pnode1), pnode->ichMin, pnode->ichLim); //PTNODE(knopArray , "arr cnst" ,None ,Uni ,fnopUni) //PTNODE(knopObject , "obj cnst" ,None ,Uni ,fnopUni) case knopArray: case knopObject: // TODO: need to copy arr Assert(false); break; // Binary operators //PTNODE(knopAdd , "+" ,Add ,Bin ,fnopBin) //PTNODE(knopSub , "-" ,Sub ,Bin ,fnopBin) //PTNODE(knopMul , "*" ,Mul ,Bin ,fnopBin) //PTNODE(knopExpo , "**" ,Expo ,Bin ,fnopBin) //PTNODE(knopDiv , "/" ,Div ,Bin ,fnopBin) //PTNODE(knopMod , "%" ,Mod ,Bin ,fnopBin) //PTNODE(knopOr , "|" ,BitOr ,Bin ,fnopBin) //PTNODE(knopXor , "^" ,BitXor ,Bin ,fnopBin) //PTNODE(knopAnd , "&" ,BitAnd ,Bin ,fnopBin) //PTNODE(knopEq , "==" ,EQ ,Bin ,fnopBin|fnopRel) //PTNODE(knopNe , "!=" ,NE ,Bin ,fnopBin|fnopRel) //PTNODE(knopLt , "<" ,LT ,Bin ,fnopBin|fnopRel) //PTNODE(knopLe , "<=" ,LE ,Bin ,fnopBin|fnopRel) //PTNODE(knopGe , ">=" ,GE ,Bin ,fnopBin|fnopRel) //PTNODE(knopGt , ">" ,GT ,Bin ,fnopBin|fnopRel) //PTNODE(knopEqv , "===" ,Eqv ,Bin ,fnopBin|fnopRel) //PTNODE(knopIn , "in" ,In ,Bin ,fnopBin|fnopRel) //PTNODE(knopInstOf , "instanceof",InstOf ,Bin ,fnopBin|fnopRel) //PTNODE(knopNEqv , "!==" ,NEqv ,Bin ,fnopBin|fnopRel) //PTNODE(knopComma , "," ,None ,Bin ,fnopBin) //PTNODE(knopLogOr , "||" ,None ,Bin ,fnopBin) //PTNODE(knopLogAnd , "&&" ,None ,Bin ,fnopBin) //PTNODE(knopLsh , "<<" ,Lsh ,Bin ,fnopBin) //PTNODE(knopRsh , ">>" ,Rsh ,Bin ,fnopBin) //PTNODE(knopRs2 , ">>>" ,Rs2 ,Bin ,fnopBin) case knopAdd: case knopSub: case knopMul: case knopExpo: case knopDiv: case knopMod: case knopOr: case knopXor: case knopAnd: case knopEq: case knopNe: case knopLt: case knopLe: case knopGe: case knopGt: case knopEqv: case knopIn: case knopInstOf: case knopNEqv: case knopComma: case knopLogOr: case knopLogAnd: case knopLsh: case knopRsh: case knopRs2: //PTNODE(knopAsg , "=" ,None ,Bin ,fnopBin|fnopAsg) case knopAsg: //PTNODE(knopDot , "." ,None ,Bin ,fnopBin) case knopDot: //PTNODE(knopAsgAdd , "+=" ,Add ,Bin ,fnopBin|fnopAsg) case knopAsgAdd: //PTNODE(knopAsgSub , "-=" ,Sub ,Bin ,fnopBin|fnopAsg) case knopAsgSub: //PTNODE(knopAsgMul , "*=" ,Mul ,Bin ,fnopBin|fnopAsg) case knopAsgMul: //PTNODE(knopAsgDiv , "/=" ,Div ,Bin ,fnopBin|fnopAsg) case knopAsgExpo: //PTNODE(knopAsgExpo , "**=" ,Expo ,Bin ,fnopBin|fnopAsg) case knopAsgDiv: //PTNODE(knopAsgMod , "%=" ,Mod ,Bin ,fnopBin|fnopAsg) case knopAsgMod: //PTNODE(knopAsgAnd , "&=" ,BitAnd ,Bin ,fnopBin|fnopAsg) case knopAsgAnd: //PTNODE(knopAsgXor , "^=" ,BitXor ,Bin ,fnopBin|fnopAsg) case knopAsgXor: //PTNODE(knopAsgOr , "|=" ,BitOr ,Bin ,fnopBin|fnopAsg) case knopAsgOr: //PTNODE(knopAsgLsh , "<<=" ,Lsh ,Bin ,fnopBin|fnopAsg) case knopAsgLsh: //PTNODE(knopAsgRsh , ">>=" ,Rsh ,Bin ,fnopBin|fnopAsg) case knopAsgRsh: //PTNODE(knopAsgRs2 , ">>>=" ,Rs2 ,Bin ,fnopBin|fnopAsg) case knopAsgRs2: //PTNODE(knopMember , ":" ,None ,Bin ,fnopBin) case knopMember: case knopMemberShort: //PTNODE(knopIndex , "[]" ,None ,Bin ,fnopBin) //PTNODE(knopList , "" ,None ,Bin ,fnopNone) case knopIndex: case knopList: return CreateBinNode(pnode->nop, CopyPnode(pnode->AsParseNodeBin()->pnode1), CopyPnode(pnode->AsParseNodeBin()->pnode2), pnode->ichMin, pnode->ichLim); //PTNODE(knopCall , "()" ,None ,Bin ,fnopBin) //PTNODE(knopNew , "new" ,None ,Bin ,fnopBin) case knopNew: case knopCall: return CreateCallNode(pnode->nop, CopyPnode(pnode->AsParseNodeCall()->pnodeTarget), CopyPnode(pnode->AsParseNodeCall()->pnodeArgs), pnode->ichMin, pnode->ichLim); //PTNODE(knopQmark , "?" ,None ,Tri ,fnopBin) case knopQmark: return CreateTriNode(pnode->nop, CopyPnode(pnode->AsParseNodeTri()->pnode1), CopyPnode(pnode->AsParseNodeTri()->pnode2), CopyPnode(pnode->AsParseNodeTri()->pnode3), pnode->ichMin, pnode->ichLim); // General nodes. //PTNODE(knopVarDecl , "varDcl" ,None ,Var ,fnopNone) case knopVarDecl: { ParseNodeVar* copyNode = Anew(&m_nodeAllocator, ParseNodeVar, knopVarDecl, pnode->ichMin, pnode->ichLim, nullptr); copyNode->pnodeInit = CopyPnode(pnode->AsParseNodeVar()->pnodeInit); copyNode->sym = pnode->AsParseNodeVar()->sym; // TODO: mult-decl Assert(pnode->AsParseNodeVar()->pnodeNext == NULL); copyNode->pnodeNext = NULL; return copyNode; } //PTNODE(knopFncDecl , "fncDcl" ,None ,Fnc ,fnopLeaf) //PTNODE(knopProg , "program" ,None ,Fnc ,fnopNone) case knopFncDecl: case knopProg: Assert(false); break; //PTNODE(knopEndCode , "" ,None ,None ,fnopNone) case knopEndCode: break; //PTNODE(knopDebugger , "debugger" ,None ,None ,fnopNone) case knopDebugger: break; //PTNODE(knopFor , "for" ,None ,For ,fnopBreak|fnopContinue) case knopFor: { ParseNode* copyNode = CreateNodeForOpT(pnode->ichMin, pnode->ichLim); copyNode->AsParseNodeFor()->pnodeInverted = NULL; copyNode->AsParseNodeFor()->pnodeInit = CopyPnode(pnode->AsParseNodeFor()->pnodeInit); copyNode->AsParseNodeFor()->pnodeCond = CopyPnode(pnode->AsParseNodeFor()->pnodeCond); copyNode->AsParseNodeFor()->pnodeIncr = CopyPnode(pnode->AsParseNodeFor()->pnodeIncr); copyNode->AsParseNodeFor()->pnodeBody = CopyPnode(pnode->AsParseNodeFor()->pnodeBody); return copyNode; } //PTNODE(knopIf , "if" ,None ,If ,fnopNone) case knopIf: Assert(false); break; //PTNODE(knopWhile , "while" ,None ,While,fnopBreak|fnopContinue) case knopWhile: Assert(false); break; //PTNODE(knopDoWhile , "do-while" ,None ,While,fnopBreak|fnopContinue) case knopDoWhile: Assert(false); break; //PTNODE(knopForIn , "for in" ,None ,ForIn,fnopBreak|fnopContinue|fnopCleanup) case knopForIn: Assert(false); break; case knopForOf: Assert(false); break; //PTNODE(knopReturn , "return" ,None ,Uni ,fnopNone) case knopReturn: { ParseNode* copyNode = CreateNodeForOpT(pnode->ichMin, pnode->ichLim); copyNode->AsParseNodeReturn()->pnodeExpr = CopyPnode(pnode->AsParseNodeReturn()->pnodeExpr); return copyNode; } //PTNODE(knopBlock , "{}" ,None ,Block,fnopNone) case knopBlock: { ParseNodeBlock* copyNode = CreateBlockNode(pnode->ichMin, pnode->ichLim, pnode->AsParseNodeBlock()->blockType); if (pnode->grfpn & PNodeFlags::fpnSyntheticNode) { // fpnSyntheticNode is sometimes set on PnodeBlockType::Regular blocks which // CreateBlockNode() will not automatically set for us, so set it here if it's // specified on the source node. copyNode->grfpn |= PNodeFlags::fpnSyntheticNode; } copyNode->pnodeStmt = CopyPnode(pnode->AsParseNodeBlock()->pnodeStmt); return copyNode; } //PTNODE(knopWith , "with" ,None ,With ,fnopCleanup) case knopWith: Assert(false); break; //PTNODE(knopBreak , "break" ,None ,Jump ,fnopNone) case knopBreak: Assert(false); break; //PTNODE(knopContinue , "continue" ,None ,Jump ,fnopNone) case knopContinue: Assert(false); break; //PTNODE(knopSwitch , "switch" ,None ,Switch,fnopBreak) case knopSwitch: Assert(false); break; //PTNODE(knopCase , "case" ,None ,Case ,fnopNone) case knopCase: Assert(false); break; //PTNODE(knopTryFinally,"try-finally",None,TryFinally,fnopCleanup) case knopTryFinally: Assert(false); break; case knopFinally: Assert(false); break; //PTNODE(knopCatch , "catch" ,None ,Catch,fnopNone) case knopCatch: Assert(false); break; //PTNODE(knopTryCatch , "try-catch" ,None ,TryCatch ,fnopCleanup) case knopTryCatch: Assert(false); break; //PTNODE(knopTry , "try" ,None ,Try ,fnopCleanup) case knopTry: Assert(false); break; //PTNODE(knopThrow , "throw" ,None ,Uni ,fnopNone) case knopThrow: Assert(false); break; default: Assert(false); break; } return NULL; } // Returns true when str is string for Nan, Infinity or -Infinity. // Does not check for double number value being in NaN/Infinity range. // static template inline bool Parser::IsNaNOrInfinityLiteral(LPCOLESTR str) { // Note: wcscmp crashes when one of the parameters is NULL. return str && (wcscmp(_u("NaN"), str) == 0 || wcscmp(_u("Infinity"), str) == 0 || (CheckForNegativeInfinity && wcscmp(_u("-Infinity"), str) == 0)); } template IdentPtr Parser::ParseSuper(bool fAllowCall) { ParseNodeFnc * currentNodeFunc = GetCurrentFunctionNode(); ParseNodeFnc * currentNonLambdaFunc = GetCurrentNonLambdaFunctionNode(); IdentPtr superPid = nullptr; switch (m_token.tk) { case tkDot: // super.prop case tkLBrack: // super[foo] superPid = wellKnownPropertyPids._super; break; case tkLParen: // super(args) superPid = wellKnownPropertyPids._superConstructor; break; default: Error(ERRInvalidSuper); break; } currentNodeFunc->SetHasSuperReference(TRUE); CHAKRATEL_LANGSTATS_INC_LANGFEATURECOUNT(ES6, Super, m_scriptContext); // If we are defer parsing, we can skip verifying that the super reference is valid. // If it wasn't the parser would have thrown during upfront parsing and we wouldn't be defer parsing the function. if (m_parseType == ParseType_Deferred) { return superPid; } if (!fAllowCall && (m_token.tk == tkLParen)) { Error(ERRInvalidSuper); // new super() is not allowed } else if ((currentNodeFunc->IsConstructor() && currentNodeFunc->superRestrictionState == SuperRestrictionState::CallAndPropertyAllowed) || (currentNonLambdaFunc != nullptr && currentNonLambdaFunc->superRestrictionState == SuperRestrictionState::CallAndPropertyAllowed)) { // Any super access is good within a class constructor } else if ((this->m_grfscr & fscrEval) == fscrEval || (currentNonLambdaFunc != nullptr && currentNonLambdaFunc->superRestrictionState == SuperRestrictionState::PropertyAllowed)) { // Currently for eval cases during compile time we use propertyallowed and throw during runtime for error cases if (m_token.tk == tkLParen) { if ((this->m_grfscr & fscrEval) == fscrNil) { // Cannot call super within a class member Error(ERRInvalidSuper); } else { Js::JavascriptFunction * caller = nullptr; if (Js::JavascriptStackWalker::GetCaller(&caller, m_scriptContext)) { Js::FunctionBody * callerBody = caller->GetFunctionBody(); Assert(callerBody); if (!callerBody->GetFunctionInfo()->GetAllowDirectSuper()) { Error(ERRInvalidSuper); } } } } } else { // Anything else is an error Error(ERRInvalidSuper); } return superPid; } void Parser::AppendToList(ParseNodePtr *node, ParseNodePtr nodeToAppend) { Assert(nodeToAppend); ParseNodePtr* lastPtr = node; while ((*lastPtr) && (*lastPtr)->nop == knopList) { lastPtr = &(*lastPtr)->AsParseNodeBin()->pnode2; } auto last = (*lastPtr); if (last) { *lastPtr = CreateBinNode(knopList, last, nodeToAppend, last->ichMin, nodeToAppend->ichLim); } else { *lastPtr = nodeToAppend; } } ParseNodePtr Parser::ConvertArrayToArrayPattern(ParseNodePtr pnode) { Assert(pnode->nop == knopArray); pnode->nop = knopArrayPattern; ForEachItemRefInList(&pnode->AsParseNodeArrLit()->pnode1, [&](ParseNodePtr *itemRef) { ParseNodePtr item = *itemRef; if (item->nop == knopEllipsis) { itemRef = &item->AsParseNodeUni()->pnode1; item = *itemRef; if (!(item->nop == knopName || item->nop == knopDot || item->nop == knopIndex || item->nop == knopArray || item->nop == knopObject)) { Error(ERRInvalidAssignmentTarget); } } else if (item->nop == knopAsg) { itemRef = &item->AsParseNodeBin()->pnode1; item = *itemRef; } if (item->nop == knopArray) { ConvertArrayToArrayPattern(item); } else if (item->nop == knopObject) { *itemRef = ConvertObjectToObjectPattern(item); } else if (item->nop == knopName) { TrackAssignment(item, nullptr); } }); return pnode; } ParseNodeUni * Parser::ConvertObjectToObjectPattern(ParseNodePtr pnodeMemberList) { charcount_t ichMin = this->GetScanner()->IchMinTok(); charcount_t ichLim = this->GetScanner()->IchLimTok(); if (pnodeMemberList != nullptr && pnodeMemberList->nop == knopObject) { ichMin = pnodeMemberList->ichMin; ichLim = pnodeMemberList->ichLim; pnodeMemberList = pnodeMemberList->AsParseNodeUni()->pnode1; } ParseNodeObjLit * objectPatternNode = CreateObjectPatternNode(pnodeMemberList, ichMin, ichLim, true/*convertToPattern*/); return objectPatternNode; } ParseNodePtr Parser::GetRightSideNodeFromPattern(ParseNodePtr pnode) { Assert(pnode != nullptr); ParseNodePtr rightNode = nullptr; OpCode op = pnode->nop; if (op == knopObject) { rightNode = ConvertObjectToObjectPattern(pnode); } else if (op == knopArray) { rightNode = ConvertArrayToArrayPattern(pnode); } else { rightNode = pnode; if (op == knopName) { TrackAssignment(pnode, nullptr); } else if (op == knopAsg) { TrackAssignment(pnode->AsParseNodeBin()->pnode1, nullptr); } } return rightNode; } ParseNodePtr Parser::ConvertMemberToMemberPattern(ParseNodePtr pnodeMember) { if (pnodeMember->nop == knopObjectPatternMember || pnodeMember->nop == knopEllipsis) { return pnodeMember; } Assert(pnodeMember->nop == knopMember || pnodeMember->nop == knopMemberShort); ParseNodePtr rightNode = GetRightSideNodeFromPattern(pnodeMember->AsParseNodeBin()->pnode2); ParseNodePtr resultNode = CreateBinNode(knopObjectPatternMember, pnodeMember->AsParseNodeBin()->pnode1, rightNode); resultNode->ichMin = pnodeMember->ichMin; resultNode->ichLim = pnodeMember->ichLim; return resultNode; } ParseNodePtr Parser::ConvertToPattern(ParseNodePtr pnode) { if (pnode != nullptr) { if (pnode->nop == knopArray) { ConvertArrayToArrayPattern(pnode); } else if (pnode->nop == knopObject) { pnode = ConvertObjectToObjectPattern(pnode); } } return pnode; } // This essentially be called for verifying the structure of the current tree with satisfying the destructuring grammar. void Parser::ParseDestructuredLiteralWithScopeSave(tokens declarationType, bool isDecl, bool topLevel, DestructuringInitializerContext initializerContext/* = DIC_None*/, bool allowIn /*= true*/) { // We are going to parse the text again to validate the current grammar as Destructuring. Saving some scopes and // AST related information before the validation parsing and later they will be restored. ParseNodeFnc * pnodeFncSave = m_currentNodeFunc; ParseNodeFnc * pnodeDeferredFncSave = m_currentNodeDeferredFunc; if (m_currentNodeDeferredFunc == nullptr) { m_currentNodeDeferredFunc = m_currentNodeFunc; } int32 *pAstSizeSave = m_pCurrentAstSize; uint *pNestedCountSave = m_pnestedCount; ParseNodePtr *ppnodeScopeSave = m_ppnodeScope; ParseNodePtr *ppnodeExprScopeSave = m_ppnodeExprScope; ParseNodePtr newTempScope = nullptr; m_ppnodeScope = &newTempScope; int32 newTempAstSize = 0; m_pCurrentAstSize = &newTempAstSize; uint newTempNestedCount = 0; m_pnestedCount = &newTempNestedCount; m_ppnodeExprScope = nullptr; charcount_t funcInArraySave = m_funcInArray; uint funcInArrayDepthSave = m_funcInArrayDepth; // we need to reset this as we are going to parse the grammar again. m_hasDeferredShorthandInitError = false; ParseDestructuredLiteral(declarationType, isDecl, topLevel, initializerContext, allowIn); m_currentNodeFunc = pnodeFncSave; m_currentNodeDeferredFunc = pnodeDeferredFncSave; m_pCurrentAstSize = pAstSizeSave; m_pnestedCount = pNestedCountSave; m_ppnodeScope = ppnodeScopeSave; m_ppnodeExprScope = ppnodeExprScopeSave; m_funcInArray = funcInArraySave; m_funcInArrayDepth = funcInArrayDepthSave; } template ParseNodePtr Parser::ParseDestructuredLiteral(tokens declarationType, bool isDecl, bool topLevel/* = true*/, DestructuringInitializerContext initializerContext/* = DIC_None*/, bool allowIn/* = true*/, BOOL *forInOfOkay/* = nullptr*/, BOOL *nativeForOkay/* = nullptr*/) { ParseNodeUni * pnode = nullptr; Assert(IsPossiblePatternStart()); if (m_token.tk == tkLCurly) { pnode = ParseDestructuredObjectLiteral(declarationType, isDecl, topLevel); } else { pnode = ParseDestructuredArrayLiteral(declarationType, isDecl, topLevel); } return ParseDestructuredInitializer(pnode, isDecl, topLevel, initializerContext, allowIn, forInOfOkay, nativeForOkay); } template ParseNodePtr Parser::ParseDestructuredInitializer(ParseNodeUni * lhsNode, bool isDecl, bool topLevel, DestructuringInitializerContext initializerContext, bool allowIn, BOOL *forInOfOkay, BOOL *nativeForOkay) { this->GetScanner()->Scan(); if (topLevel && nativeForOkay == nullptr) { if (initializerContext != DIC_ForceErrorOnInitializer && m_token.tk != tkAsg) { // e.g. var {x}; Error(ERRDestructInit); } else if (initializerContext == DIC_ForceErrorOnInitializer && m_token.tk == tkAsg) { // e.g. catch([x] = [0]) Error(ERRDestructNotInit); } } if (m_token.tk != tkAsg || initializerContext == DIC_ShouldNotParseInitializer) { if (topLevel && nativeForOkay != nullptr) { // Native loop should have destructuring initializer *nativeForOkay = FALSE; } return lhsNode; } if (forInOfOkay) { *forInOfOkay = FALSE; } this->GetScanner()->Scan(); bool alreadyHasInitError = m_hasDeferredShorthandInitError; ParseNodePtr pnodeDefault = ParseExpr(koplCma, nullptr, allowIn); if (m_hasDeferredShorthandInitError && !alreadyHasInitError) { Error(ERRnoColon); } ParseNodeBin * pnodeDestructAsg = nullptr; if (buildAST) { Assert(lhsNode != nullptr); pnodeDestructAsg = CreateBinNode(knopAsg, lhsNode, pnodeDefault, lhsNode->ichMin, pnodeDefault->ichLim); } return pnodeDestructAsg; } template ParseNodeUni * Parser::ParseDestructuredObjectLiteral(tokens declarationType, bool isDecl, bool topLevel/* = true*/) { Assert(m_token.tk == tkLCurly); charcount_t ichMin = this->GetScanner()->IchMinTok(); this->GetScanner()->Scan(); if (!isDecl) { declarationType = tkLCurly; } ParseNodePtr pnodeMemberList = ParseMemberList(nullptr/*pNameHint*/, nullptr/*pHintLength*/, declarationType); charcount_t ichLim = this->GetScanner()->IchLimTok(); ParseNodeObjLit * objectPatternNode = buildAST ? CreateObjectPatternNode(pnodeMemberList, ichMin, ichLim) : nullptr; Assert(m_token.tk == tkRCurly); return objectPatternNode; } template ParseNodePtr Parser::ParseDestructuredVarDecl(tokens declarationType, bool isDecl, bool *hasSeenRest, bool topLevel/* = true*/, bool allowEmptyExpression/* = true*/, bool isObjectPattern/* =false*/) { ParseNodePtr pnodeElem = nullptr; int parenCount = 0; bool seenRest = false; // Save the Block ID prior to the increments, so we can restore it back. int originalCurrentBlockId = GetCurrentBlock()->blockId; // Eat the left parentheses only when its not a declaration. This will make sure we throw syntax errors early. if (!isDecl) { while (m_token.tk == tkLParen) { this->GetScanner()->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()->blockId = m_nextBlockId++; } } if (m_token.tk == tkEllipsis) { // As per ES 2015 : Rest can have left-hand-side-expression when on assignment expression, but under declaration only binding identifier is allowed // But spec is going to change for this one to allow LHS-expression both on expression and declaration - so making that happen early. seenRest = true; this->GetScanner()->Scan(); // Eat the left parentheses only when its not a declaration. This will make sure we throw syntax errors early. if (!isDecl) { while (m_token.tk == tkLParen) { this->GetScanner()->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()->blockId = m_nextBlockId++; } } if (m_token.tk != tkID && m_token.tk != tkTHIS && m_token.tk != tkSUPER) { bool nestedDestructuring = m_token.tk == tkLCurly || m_token.tk == tkLBrack; if ((isObjectPattern && nestedDestructuring) || (!isObjectPattern && !nestedDestructuring)) { if (isDecl) { Error(ERRnoIdent); } else { Error(ERRInvalidAssignmentTarget); } } } } if (IsPossiblePatternStart()) { // For the possible pattern start we do not allow the parens before if (parenCount != 0) { Error(ERRDestructIDRef); } // Go recursively pnodeElem = ParseDestructuredLiteral(declarationType, isDecl, false /*topLevel*/, seenRest ? DIC_ShouldNotParseInitializer : DIC_None); if (!isDecl) { BOOL fCanAssign; IdentToken token; // Look for postfix operator pnodeElem = ParsePostfixOperators(pnodeElem, TRUE, FALSE, FALSE, TRUE, &fCanAssign, &token); } } else if (m_token.tk == tkSUPER || m_token.tk == tkID || m_token.tk == tkTHIS) { if (isDecl) { charcount_t ichMin = this->GetScanner()->IchMinTok(); pnodeElem = ParseVariableDeclaration(declarationType, ichMin ,/* fAllowIn */false, /* pfForInOk */nullptr, /* singleDefOnly */true, /* allowInit */!seenRest, false /*topLevelParse*/); } else { BOOL fCanAssign; IdentToken token; // We aren't declaring anything, so scan the ID reference manually. pnodeElem = ParseTerm(/* fAllowCall */ m_token.tk != tkSUPER, nullptr /*pNameHint*/, nullptr /*pHintLength*/, nullptr /*pShortNameOffset*/, &token, false, FALSE, &fCanAssign); // In this destructuring case we can force error here as we cannot assign. if (!fCanAssign) { Error(ERRInvalidAssignmentTarget); } if (buildAST) { if (IsStrictMode() && pnodeElem != nullptr && pnodeElem->nop == knopName) { CheckStrictModeEvalArgumentsUsage(pnodeElem->AsParseNodeName()->pid); } } else { if (IsStrictMode() && token.tk == tkID) { CheckStrictModeEvalArgumentsUsage(token.pid); } token.tk = tkNone; } } } else if (!((m_token.tk == tkComma || m_token.tk == tkRBrack || m_token.tk == tkRCurly) && allowEmptyExpression)) { if (m_token.IsOperator()) { Error(ERRDestructNoOper); } Error(ERRDestructIDRef); } // Swallow RParens before a default expression, if any. // 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) { while (m_token.tk == tkRParen) { this->GetScanner()->Scan(); --parenCount; } // Restore the Block ID of the current block after the parsing of destructured variable declarations and initializers. GetCurrentBlock()->blockId = originalCurrentBlockId; } if (parenCount != 0) { Error(ERRnoRparen); } if (hasSeenRest != nullptr) { *hasSeenRest = seenRest; } if (m_token.tk == tkAsg) { // Parse the initializer. if (seenRest) { Error(ERRRestWithDefault); } this->GetScanner()->Scan(); bool alreadyHasInitError = m_hasDeferredShorthandInitError; ParseNodePtr pnodeInit = ParseExpr(koplCma); if (m_hasDeferredShorthandInitError && !alreadyHasInitError) { Error(ERRnoColon); } if (buildAST) { pnodeElem = CreateBinNode(knopAsg, pnodeElem, pnodeInit); } } if (buildAST && seenRest) { ParseNodePtr pnodeRest = CreateUniNode(knopEllipsis, pnodeElem); pnodeElem = pnodeRest; } if (!(m_token.tk == tkComma || m_token.tk == tkRBrack || m_token.tk == tkRCurly)) { if (m_token.IsOperator()) { Error(ERRDestructNoOper); } Error(ERRsyntax); } return pnodeElem; } template ParseNodeUni * Parser::ParseDestructuredArrayLiteral(tokens declarationType, bool isDecl, bool topLevel) { Assert(m_token.tk == tkLBrack); charcount_t ichMin = this->GetScanner()->IchMinTok(); this->GetScanner()->Scan(); ParseNodeArrLit * pnodeDestructArr = nullptr; ParseNodePtr pnodeList = nullptr; ParseNodePtr *lastNodeRef = nullptr; uint count = 0; bool hasMissingValues = false; bool seenRest = false; if (m_token.tk != tkRBrack) { while (true) { ParseNodePtr pnodeElem = ParseDestructuredVarDecl(declarationType, isDecl, &seenRest, topLevel); if (buildAST) { if (pnodeElem == nullptr && buildAST) { pnodeElem = CreateNodeForOpT(); hasMissingValues = true; } AddToNodeListEscapedUse(&pnodeList, &lastNodeRef, pnodeElem); } count++; if (m_token.tk == tkRBrack) { break; } if (m_token.tk != tkComma) { Error(ERRDestructNoOper); } if (seenRest) // Rest must be in the last position. { Error(ERRDestructRestLast); } this->GetScanner()->Scan(); // break if we have the trailing comma as well, eg. [a,] if (m_token.tk == tkRBrack) { break; } } } if (buildAST) { pnodeDestructArr = CreateNodeForOpT(ichMin); pnodeDestructArr->pnode1 = pnodeList; pnodeDestructArr->arrayOfTaggedInts = false; pnodeDestructArr->arrayOfInts = false; pnodeDestructArr->arrayOfNumbers = false; pnodeDestructArr->hasMissingValues = hasMissingValues; pnodeDestructArr->count = count; pnodeDestructArr->spreadCount = seenRest ? 1 : 0; if (pnodeDestructArr->pnode1) { this->CheckArguments(pnodeDestructArr->pnode1); } } return pnodeDestructArr; } void Parser::CaptureContext(ParseContext *parseContext) const { parseContext->pszSrc = this->GetScanner()->PchBase(); parseContext->length = this->m_originalLength; parseContext->characterOffset = this->GetScanner()->IchMinTok(); parseContext->offset = parseContext->characterOffset + this->GetScanner()->m_cMultiUnits; parseContext->grfscr = this->m_grfscr; parseContext->lineNumber = this->GetScanner()->LineCur(); parseContext->pnodeProg = this->m_currentNodeProg; parseContext->isUtf8 = this->GetScanner()->IsUtf8(); parseContext->strictMode = this->IsStrictMode(); parseContext->sourceContextInfo = this->m_sourceContextInfo; parseContext->currentBlockInfo = this->m_currentBlockInfo; parseContext->nextBlockId = this->m_nextBlockId; } void Parser::RestoreContext(ParseContext *const parseContext) { m_sourceContextInfo = parseContext->sourceContextInfo; m_currentBlockInfo = parseContext->currentBlockInfo; m_nextBlockId = parseContext->nextBlockId; m_grfscr = parseContext->grfscr; m_length = parseContext->length; this->GetScanner()->SetText(parseContext->pszSrc, parseContext->offset, parseContext->length, parseContext->characterOffset, parseContext->isUtf8, parseContext->grfscr, parseContext->lineNumber); m_currentNodeProg = parseContext->pnodeProg; m_fUseStrictMode = parseContext->strictMode; } class ByteCodeGenerator; #if DBG_DUMP #define INDENT_SIZE 2 void PrintPnodeListWIndent(ParseNode *pnode, int indentAmt); void PrintFormalsWIndent(ParseNode *pnode, int indentAmt); void Indent(int indentAmt) { for (int i = 0; i < indentAmt; i++) { Output::Print(_u(" ")); } } void PrintBlockType(PnodeBlockType type) { switch (type) { case Global: Output::Print(_u("(Global)")); break; case Function: Output::Print(_u("(Function)")); break; case Regular: Output::Print(_u("(Regular)")); break; case Parameter: Output::Print(_u("(Parameter)")); break; default: Output::Print(_u("(unknown blocktype)")); break; } } void PrintScopesWIndent(ParseNode *pnode, int indentAmt) { ParseNode *scope = nullptr; bool firstOnly = false; switch (pnode->nop) { case knopProg: case knopFncDecl: scope = pnode->AsParseNodeFnc()->pnodeScopes; break; case knopBlock: scope = pnode->AsParseNodeBlock()->pnodeScopes; break; case knopCatch: scope = pnode->AsParseNodeCatch()->pnodeScopes; break; case knopWith: scope = pnode->AsParseNodeWith()->pnodeScopes; break; case knopSwitch: scope = pnode->AsParseNodeSwitch()->pnodeBlock; firstOnly = true; break; case knopFor: scope = pnode->AsParseNodeFor()->pnodeBlock; firstOnly = true; break; case knopForIn: scope = pnode->AsParseNodeForInOrForOf()->pnodeBlock; firstOnly = true; break; case knopForOf: scope = pnode->AsParseNodeForInOrForOf()->pnodeBlock; firstOnly = true; break; } if (scope) { Output::Print(_u("[%4d, %4d): "), scope->ichMin, scope->ichLim); Indent(indentAmt); Output::Print(_u("Scopes: ")); ParseNode *next = nullptr; ParseNode *syntheticBlock = nullptr; while (scope) { switch (scope->nop) { case knopFncDecl: Output::Print(_u("knopFncDecl")); next = scope->AsParseNodeFnc()->pnodeNext; break; case knopBlock: Output::Print(_u("knopBlock")); PrintBlockType(scope->AsParseNodeBlock()->blockType); next = scope->AsParseNodeBlock()->pnodeNext; break; case knopCatch: Output::Print(_u("knopCatch")); next = scope->AsParseNodeCatch()->pnodeNext; break; case knopWith: Output::Print(_u("knopWith")); next = scope->AsParseNodeWith()->pnodeNext; break; default: Output::Print(_u("unknown")); break; } if (firstOnly) { next = nullptr; syntheticBlock = scope; } if (scope->grfpn & fpnSyntheticNode) { Output::Print(_u(" synthetic")); if (scope->nop == knopBlock) syntheticBlock = scope; } Output::Print(_u(" (%d-%d)"), scope->ichMin, scope->ichLim); if (next) Output::Print(_u(", ")); scope = next; } Output::Print(_u("\n")); if (syntheticBlock || firstOnly) { PrintScopesWIndent(syntheticBlock, indentAmt + INDENT_SIZE); } } } void PrintPnodeWIndent(ParseNode *pnode, int indentAmt) { if (pnode == NULL) return; Output::Print(_u("[%4d, %4d): "), pnode->ichMin, pnode->ichLim); switch (pnode->nop) { //PTNODE(knopName , "name" ,None ,Pid ,fnopLeaf) case knopName: Indent(indentAmt); if (pnode->AsParseNodeName()->pid != NULL) { Output::Print(_u("id: %s\n"), pnode->AsParseNodeName()->pid->Psz()); } else { Output::Print(_u("name node\n")); } break; //PTNODE(knopInt , "int const" ,None ,Int ,fnopLeaf|fnopConst) case knopInt: Indent(indentAmt); Output::Print(_u("%d\n"), pnode->AsParseNodeInt()->lw); break; //PTNODE(knopFlt , "flt const" ,None ,Flt ,fnopLeaf|fnopConst) case knopFlt: Indent(indentAmt); Output::Print(_u("%lf\n"), pnode->AsParseNodeFloat()->dbl); break; //PTNODE(knopStr , "str const" ,None ,Pid ,fnopLeaf|fnopConst) case knopStr: Indent(indentAmt); Output::Print(_u("\"%s\"\n"), pnode->AsParseNodeStr()->pid->Psz()); break; //PTNODE(knopRegExp , "reg expr" ,None ,Pid ,fnopLeaf|fnopConst) case knopRegExp: Indent(indentAmt); Output::Print(_u("/%x/\n"), pnode->AsParseNodeRegExp()->regexPattern); break; //PTNODE(knopNull , "null" ,Null ,None ,fnopLeaf) case knopNull: Indent(indentAmt); Output::Print(_u("null\n")); break; //PTNODE(knopFalse , "false" ,False ,None ,fnopLeaf) case knopFalse: Indent(indentAmt); Output::Print(_u("false\n")); break; //PTNODE(knopTrue , "true" ,True ,None ,fnopLeaf) case knopTrue: Indent(indentAmt); Output::Print(_u("true\n")); break; //PTNODE(knopEmpty , "empty" ,Empty ,None ,fnopLeaf) case knopEmpty: Indent(indentAmt); Output::Print(_u("empty\n")); break; // Unary operators. //PTNODE(knopNot , "~" ,BitNot ,Uni ,fnopUni) case knopNot: Indent(indentAmt); Output::Print(_u("~\n")); PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; //PTNODE(knopNeg , "unary -" ,Neg ,Uni ,fnopUni) case knopNeg: Indent(indentAmt); Output::Print(_u("U-\n")); PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; //PTNODE(knopPos , "unary +" ,Pos ,Uni ,fnopUni) case knopPos: Indent(indentAmt); Output::Print(_u("U+\n")); PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; //PTNODE(knopLogNot , "!" ,LogNot ,Uni ,fnopUni) case knopLogNot: Indent(indentAmt); Output::Print(_u("!\n")); PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; //PTNODE(knopEllipsis , "..." ,Spread ,Uni , fnopUni) case knopEllipsis: Indent(indentAmt); Output::Print(_u("...\n")); PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; //PTNODE(knopIncPost , "++ post" ,Inc ,Uni ,fnopUni|fnopAsg) case knopIncPost: Indent(indentAmt); Output::Print(_u("++\n")); PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; //PTNODE(knopDecPost , "-- post" ,Dec ,Uni ,fnopUni|fnopAsg) case knopDecPost: Indent(indentAmt); Output::Print(_u("--\n")); PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; //PTNODE(knopIncPre , "++ pre" ,Inc ,Uni ,fnopUni|fnopAsg) case knopIncPre: Indent(indentAmt); Output::Print(_u("++\n")); PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; //PTNODE(knopDecPre , "-- pre" ,Dec ,Uni ,fnopUni|fnopAsg) case knopDecPre: Indent(indentAmt); Output::Print(_u("--\n")); PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; //PTNODE(knopTypeof , "typeof" ,None ,Uni ,fnopUni) case knopTypeof: Indent(indentAmt); Output::Print(_u("typeof\n")); PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; //PTNODE(knopVoid , "void" ,Void ,Uni ,fnopUni) case knopVoid: Indent(indentAmt); Output::Print(_u("void\n")); PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; //PTNODE(knopDelete , "delete" ,None ,Uni ,fnopUni) case knopDelete: Indent(indentAmt); Output::Print(_u("delete\n")); PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; //PTNODE(knopArray , "arr cnst" ,None ,Uni ,fnopUni) case knopArrayPattern: Indent(indentAmt); Output::Print(_u("Array Pattern\n")); PrintPnodeListWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; case knopObjectPattern: Indent(indentAmt); Output::Print(_u("Object Pattern\n")); PrintPnodeListWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; case knopArray: Indent(indentAmt); Output::Print(_u("Array Literal\n")); PrintPnodeListWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; //PTNODE(knopObject , "obj cnst" ,None ,Uni ,fnopUni) case knopObject: Indent(indentAmt); Output::Print(_u("Object Literal\n")); PrintPnodeListWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; // Binary and Ternary Operators //PTNODE(knopAdd , "+" ,Add ,Bin ,fnopBin) case knopAdd: Indent(indentAmt); Output::Print(_u("+\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopSub , "-" ,Sub ,Bin ,fnopBin) case knopSub: Indent(indentAmt); Output::Print(_u("-\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopMul , "*" ,Mul ,Bin ,fnopBin) case knopMul: Indent(indentAmt); Output::Print(_u("*\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopDiv , "/" ,Div ,Bin ,fnopBin) case knopExpo: Indent(indentAmt); Output::Print(_u("**\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopExpo , "**" ,Expo ,Bin ,fnopBin) case knopDiv: Indent(indentAmt); Output::Print(_u("/\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopMod , "%" ,Mod ,Bin ,fnopBin) case knopMod: Indent(indentAmt); Output::Print(_u("%%\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopOr , "|" ,BitOr ,Bin ,fnopBin) case knopOr: Indent(indentAmt); Output::Print(_u("|\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopXor , "^" ,BitXor ,Bin ,fnopBin) case knopXor: Indent(indentAmt); Output::Print(_u("^\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopAnd , "&" ,BitAnd ,Bin ,fnopBin) case knopAnd: Indent(indentAmt); Output::Print(_u("&\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopEq , "==" ,EQ ,Bin ,fnopBin|fnopRel) case knopEq: Indent(indentAmt); Output::Print(_u("==\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopNe , "!=" ,NE ,Bin ,fnopBin|fnopRel) case knopNe: Indent(indentAmt); Output::Print(_u("!=\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopLt , "<" ,LT ,Bin ,fnopBin|fnopRel) case knopLt: Indent(indentAmt); Output::Print(_u("<\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopLe , "<=" ,LE ,Bin ,fnopBin|fnopRel) case knopLe: Indent(indentAmt); Output::Print(_u("<=\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopGe , ">=" ,GE ,Bin ,fnopBin|fnopRel) case knopGe: Indent(indentAmt); Output::Print(_u(">=\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopGt , ">" ,GT ,Bin ,fnopBin|fnopRel) case knopGt: Indent(indentAmt); Output::Print(_u(">\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopCall , "()" ,None ,Bin ,fnopBin) case knopCall: Indent(indentAmt); Output::Print(_u("Call\n")); PrintPnodeWIndent(pnode->AsParseNodeCall()->pnodeTarget, indentAmt + INDENT_SIZE); PrintPnodeListWIndent(pnode->AsParseNodeCall()->pnodeArgs, indentAmt + INDENT_SIZE); break; //PTNODE(knopDot , "." ,None ,Bin ,fnopBin) case knopDot: Indent(indentAmt); Output::Print(_u(".\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopAsg , "=" ,None ,Bin ,fnopBin|fnopAsg) case knopAsg: Indent(indentAmt); Output::Print(_u("=\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopInstOf , "instanceof",InstOf ,Bin ,fnopBin|fnopRel) case knopInstOf: Indent(indentAmt); Output::Print(_u("instanceof\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopIn , "in" ,In ,Bin ,fnopBin|fnopRel) case knopIn: Indent(indentAmt); Output::Print(_u("in\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopEqv , "===" ,Eqv ,Bin ,fnopBin|fnopRel) case knopEqv: Indent(indentAmt); Output::Print(_u("===\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopNEqv , "!==" ,NEqv ,Bin ,fnopBin|fnopRel) case knopNEqv: Indent(indentAmt); Output::Print(_u("!==\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopComma , "," ,None ,Bin ,fnopBin) case knopComma: Indent(indentAmt); Output::Print(_u(",\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopLogOr , "||" ,None ,Bin ,fnopBin) case knopLogOr: Indent(indentAmt); Output::Print(_u("||\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopLogAnd , "&&" ,None ,Bin ,fnopBin) case knopLogAnd: Indent(indentAmt); Output::Print(_u("&&\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopLsh , "<<" ,Lsh ,Bin ,fnopBin) case knopLsh: Indent(indentAmt); Output::Print(_u("<<\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopRsh , ">>" ,Rsh ,Bin ,fnopBin) case knopRsh: Indent(indentAmt); Output::Print(_u(">>\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopRs2 , ">>>" ,Rs2 ,Bin ,fnopBin) case knopRs2: Indent(indentAmt); Output::Print(_u(">>>\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopNew , "new" ,None ,Bin ,fnopBin) case knopNew: Indent(indentAmt); Output::Print(_u("new\n")); PrintPnodeWIndent(pnode->AsParseNodeCall()->pnodeTarget, indentAmt + INDENT_SIZE); PrintPnodeListWIndent(pnode->AsParseNodeCall()->pnodeArgs, indentAmt + INDENT_SIZE); break; //PTNODE(knopIndex , "[]" ,None ,Bin ,fnopBin) case knopIndex: Indent(indentAmt); Output::Print(_u("[]\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeListWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopQmark , "?" ,None ,Tri ,fnopBin) case knopQmark: Indent(indentAmt); Output::Print(_u("?:\n")); PrintPnodeWIndent(pnode->AsParseNodeTri()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeTri()->pnode2, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeTri()->pnode3, indentAmt + INDENT_SIZE); break; //PTNODE(knopAsgAdd , "+=" ,Add ,Bin ,fnopBin|fnopAsg) case knopAsgAdd: Indent(indentAmt); Output::Print(_u("+=\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopAsgSub , "-=" ,Sub ,Bin ,fnopBin|fnopAsg) case knopAsgSub: Indent(indentAmt); Output::Print(_u("-=\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopAsgMul , "*=" ,Mul ,Bin ,fnopBin|fnopAsg) case knopAsgMul: Indent(indentAmt); Output::Print(_u("*=\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopAsgDiv , "/=" ,Div ,Bin ,fnopBin|fnopAsg) case knopAsgExpo: Indent(indentAmt); Output::Print(_u("**=\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopAsgExpo , "**=" ,Expo ,Bin ,fnopBin|fnopAsg) case knopAsgDiv: Indent(indentAmt); Output::Print(_u("/=\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopAsgMod , "%=" ,Mod ,Bin ,fnopBin|fnopAsg) case knopAsgMod: Indent(indentAmt); Output::Print(_u("%=\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopAsgAnd , "&=" ,BitAnd ,Bin ,fnopBin|fnopAsg) case knopAsgAnd: Indent(indentAmt); Output::Print(_u("&=\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopAsgXor , "^=" ,BitXor ,Bin ,fnopBin|fnopAsg) case knopAsgXor: Indent(indentAmt); Output::Print(_u("^=\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopAsgOr , "|=" ,BitOr ,Bin ,fnopBin|fnopAsg) case knopAsgOr: Indent(indentAmt); Output::Print(_u("|=\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopAsgLsh , "<<=" ,Lsh ,Bin ,fnopBin|fnopAsg) case knopAsgLsh: Indent(indentAmt); Output::Print(_u("<<=\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopAsgRsh , ">>=" ,Rsh ,Bin ,fnopBin|fnopAsg) case knopAsgRsh: Indent(indentAmt); Output::Print(_u(">>=\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; //PTNODE(knopAsgRs2 , ">>>=" ,Rs2 ,Bin ,fnopBin|fnopAsg) case knopAsgRs2: Indent(indentAmt); Output::Print(_u(">>>=\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; case knopComputedName: Indent(indentAmt); Output::Print(_u("ComputedProperty\n")); PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; //PTNODE(knopMember , ":" ,None ,Bin ,fnopBin) case knopMember: case knopMemberShort: case knopObjectPatternMember: Indent(indentAmt); Output::Print(_u(":\n")); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode2, indentAmt + INDENT_SIZE); break; // General nodes. //PTNODE(knopList , "" ,None ,Bin ,fnopNone) case knopList: Indent(indentAmt); Output::Print(_u("List\n")); PrintPnodeListWIndent(pnode, indentAmt + INDENT_SIZE); break; //PTNODE(knopVarDecl , "varDcl" ,None ,Var ,fnopNone) case knopVarDecl: Indent(indentAmt); Output::Print(_u("var %s\n"), pnode->AsParseNodeVar()->pid->Psz()); if (pnode->AsParseNodeVar()->pnodeInit != NULL) PrintPnodeWIndent(pnode->AsParseNodeVar()->pnodeInit, indentAmt + INDENT_SIZE); break; case knopConstDecl: Indent(indentAmt); Output::Print(_u("const %s\n"), pnode->AsParseNodeVar()->pid->Psz()); if (pnode->AsParseNodeVar()->pnodeInit != NULL) PrintPnodeWIndent(pnode->AsParseNodeVar()->pnodeInit, indentAmt + INDENT_SIZE); break; case knopLetDecl: Indent(indentAmt); Output::Print(_u("let %s\n"), pnode->AsParseNodeVar()->pid->Psz()); if (pnode->AsParseNodeVar()->pnodeInit != NULL) PrintPnodeWIndent(pnode->AsParseNodeVar()->pnodeInit, indentAmt + INDENT_SIZE); break; //PTNODE(knopFncDecl , "fncDcl" ,None ,Fnc ,fnopLeaf) case knopFncDecl: Indent(indentAmt); if (pnode->AsParseNodeFnc()->pid != NULL) { Output::Print(_u("fn decl %d nested %d name %s (%d-%d)\n"), pnode->AsParseNodeFnc()->IsDeclaration(), pnode->AsParseNodeFnc()->IsNested(), pnode->AsParseNodeFnc()->pid->Psz(), pnode->ichMin, pnode->ichLim); } else { Output::Print(_u("fn decl %d nested %d anonymous (%d-%d)\n"), pnode->AsParseNodeFnc()->IsDeclaration(), pnode->AsParseNodeFnc()->IsNested(), pnode->ichMin, pnode->ichLim); } PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); PrintFormalsWIndent(pnode->AsParseNodeFnc()->pnodeParams, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeFnc()->pnodeRest, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeFnc()->pnodeBody, indentAmt + INDENT_SIZE); if (pnode->AsParseNodeFnc()->pnodeBody == nullptr) { Output::Print(_u("[%4d, %4d): "), pnode->ichMin, pnode->ichLim); Indent(indentAmt + INDENT_SIZE); Output::Print(_u("\n")); } break; //PTNODE(knopProg , "program" ,None ,Fnc ,fnopNone) case knopProg: Indent(indentAmt); Output::Print(_u("program\n")); PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); PrintPnodeListWIndent(pnode->AsParseNodeFnc()->pnodeBody, indentAmt + INDENT_SIZE); break; //PTNODE(knopEndCode , "" ,None ,None ,fnopNone) case knopEndCode: Indent(indentAmt); Output::Print(_u("\n")); break; //PTNODE(knopDebugger , "debugger" ,None ,None ,fnopNone) case knopDebugger: Indent(indentAmt); Output::Print(_u("\n")); break; //PTNODE(knopFor , "for" ,None ,For ,fnopBreak|fnopContinue) case knopFor: Indent(indentAmt); Output::Print(_u("for\n")); PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeFor()->pnodeInit, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeFor()->pnodeCond, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeFor()->pnodeIncr, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeFor()->pnodeBody, indentAmt + INDENT_SIZE); break; //PTNODE(knopIf , "if" ,None ,If ,fnopNone) case knopIf: Indent(indentAmt); Output::Print(_u("if\n")); PrintPnodeWIndent(pnode->AsParseNodeIf()->pnodeCond, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeIf()->pnodeTrue, indentAmt + INDENT_SIZE); if (pnode->AsParseNodeIf()->pnodeFalse != NULL) PrintPnodeWIndent(pnode->AsParseNodeIf()->pnodeFalse, indentAmt + INDENT_SIZE); break; //PTNODE(knopWhile , "while" ,None ,While,fnopBreak|fnopContinue) case knopWhile: Indent(indentAmt); Output::Print(_u("while\n")); PrintPnodeWIndent(pnode->AsParseNodeWhile()->pnodeCond, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeWhile()->pnodeBody, indentAmt + INDENT_SIZE); break; //PTNODE(knopDoWhile , "do-while" ,None ,While,fnopBreak|fnopContinue) case knopDoWhile: Indent(indentAmt); Output::Print(_u("do\n")); PrintPnodeWIndent(pnode->AsParseNodeWhile()->pnodeCond, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeWhile()->pnodeBody, indentAmt + INDENT_SIZE); break; //PTNODE(knopForIn , "for in" ,None ,ForIn,fnopBreak|fnopContinue|fnopCleanup) case knopForIn: Indent(indentAmt); Output::Print(_u("forIn\n")); PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeForInOrForOf()->pnodeLval, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeForInOrForOf()->pnodeObj, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeForInOrForOf()->pnodeBody, indentAmt + INDENT_SIZE); break; case knopForOf: Indent(indentAmt); Output::Print(_u("forOf\n")); PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeForInOrForOf()->pnodeLval, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeForInOrForOf()->pnodeObj, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeForInOrForOf()->pnodeBody, indentAmt + INDENT_SIZE); break; //PTNODE(knopReturn , "return" ,None ,Uni ,fnopNone) case knopReturn: Indent(indentAmt); Output::Print(_u("return\n")); if (pnode->AsParseNodeReturn()->pnodeExpr != NULL) PrintPnodeWIndent(pnode->AsParseNodeReturn()->pnodeExpr, indentAmt + INDENT_SIZE); break; //PTNODE(knopBlock , "{}" ,None ,Block,fnopNone) case knopBlock: Indent(indentAmt); Output::Print(_u("block ")); if (pnode->grfpn & fpnSyntheticNode) Output::Print(_u("synthetic ")); PrintBlockType(pnode->AsParseNodeBlock()->blockType); Output::Print(_u("(%d-%d)\n"), pnode->ichMin, pnode->ichLim); PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); if (pnode->AsParseNodeBlock()->pnodeStmt != NULL) PrintPnodeWIndent(pnode->AsParseNodeBlock()->pnodeStmt, indentAmt + INDENT_SIZE); break; //PTNODE(knopWith , "with" ,None ,With ,fnopCleanup) case knopWith: Indent(indentAmt); Output::Print(_u("with (%d-%d)\n"), pnode->ichMin, pnode->ichLim); PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeWith()->pnodeObj, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeWith()->pnodeBody, indentAmt + INDENT_SIZE); break; //PTNODE(knopBreak , "break" ,None ,Jump ,fnopNone) case knopBreak: Indent(indentAmt); Output::Print(_u("break\n")); // TODO: some representation of target break; //PTNODE(knopContinue , "continue" ,None ,Jump ,fnopNone) case knopContinue: Indent(indentAmt); Output::Print(_u("continue\n")); // TODO: some representation of target break; //PTNODE(knopSwitch , "switch" ,None ,Switch,fnopBreak) case knopSwitch: Indent(indentAmt); Output::Print(_u("switch\n")); PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); for (ParseNodeCase *pnodeT = pnode->AsParseNodeSwitch()->pnodeCases; NULL != pnodeT; pnodeT = pnodeT->pnodeNext) { PrintPnodeWIndent(pnodeT, indentAmt + 2); } break; //PTNODE(knopCase , "case" ,None ,Case ,fnopNone) case knopCase: Indent(indentAmt); Output::Print(_u("case\n")); PrintPnodeWIndent(pnode->AsParseNodeCase()->pnodeExpr, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeCase()->pnodeBody, indentAmt + INDENT_SIZE); break; //PTNODE(knopTryFinally,"try-finally",None,TryFinally,fnopCleanup) case knopTryFinally: PrintPnodeWIndent(pnode->AsParseNodeTryFinally()->pnodeTry, indentAmt); PrintPnodeWIndent(pnode->AsParseNodeTryFinally()->pnodeFinally, indentAmt); break; case knopFinally: Indent(indentAmt); Output::Print(_u("finally\n")); PrintPnodeWIndent(pnode->AsParseNodeFinally()->pnodeBody, indentAmt + INDENT_SIZE); break; //PTNODE(knopCatch , "catch" ,None ,Catch,fnopNone) case knopCatch: Indent(indentAmt); Output::Print(_u("catch (%d-%d)\n"), pnode->ichMin, pnode->ichLim); PrintScopesWIndent(pnode, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeCatch()->GetParam(), indentAmt + INDENT_SIZE); // if (pnode->AsParseNodeCatch()->pnodeGuard!=NULL) // PrintPnodeWIndent(pnode->AsParseNodeCatch()->pnodeGuard,indentAmt+INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeCatch()->pnodeBody, indentAmt + INDENT_SIZE); break; //PTNODE(knopTryCatch , "try-catch" ,None ,TryCatch ,fnopCleanup) case knopTryCatch: PrintPnodeWIndent(pnode->AsParseNodeTryCatch()->pnodeTry, indentAmt); PrintPnodeWIndent(pnode->AsParseNodeTryCatch()->pnodeCatch, indentAmt); break; //PTNODE(knopTry , "try" ,None ,Try ,fnopCleanup) case knopTry: Indent(indentAmt); Output::Print(_u("try\n")); PrintPnodeWIndent(pnode->AsParseNodeTry()->pnodeBody, indentAmt + INDENT_SIZE); break; //PTNODE(knopThrow , "throw" ,None ,Uni ,fnopNone) case knopThrow: Indent(indentAmt); Output::Print(_u("throw\n")); PrintPnodeWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; //PTNODE(knopClassDecl, "classDecl", None , Class, fnopLeaf) case knopClassDecl: Indent(indentAmt); Output::Print(_u("class %s"), pnode->AsParseNodeClass()->pnodeName->pid->Psz()); if (pnode->AsParseNodeClass()->pnodeExtends != nullptr) { Output::Print(_u(" extends ")); PrintPnodeWIndent(pnode->AsParseNodeClass()->pnodeExtends, 0); } else { Output::Print(_u("\n")); } PrintPnodeWIndent(pnode->AsParseNodeClass()->pnodeConstructor, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeClass()->pnodeMembers, indentAmt + INDENT_SIZE); PrintPnodeWIndent(pnode->AsParseNodeClass()->pnodeStaticMembers, indentAmt + INDENT_SIZE); break; case knopStrTemplate: Indent(indentAmt); Output::Print(_u("string template\n")); PrintPnodeListWIndent(pnode->AsParseNodeStrTemplate()->pnodeSubstitutionExpressions, indentAmt + INDENT_SIZE); break; case knopYieldStar: Indent(indentAmt); Output::Print(_u("yield*\n")); PrintPnodeListWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; case knopYield: case knopYieldLeaf: Indent(indentAmt); Output::Print(_u("yield\n")); PrintPnodeListWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; case knopAwait: Indent(indentAmt); Output::Print(_u("await\n")); PrintPnodeListWIndent(pnode->AsParseNodeUni()->pnode1, indentAmt + INDENT_SIZE); break; case knopExportDefault: Indent(indentAmt); Output::Print(_u("export default\n")); PrintPnodeListWIndent(pnode->AsParseNodeExportDefault()->pnodeExpr, indentAmt + INDENT_SIZE); break; default: Output::Print(_u("unhandled pnode op %d\n"), pnode->nop); break; } } void PrintPnodeListWIndent(ParseNode *pnode, int indentAmt) { if (pnode != NULL) { while (pnode->nop == knopList) { PrintPnodeWIndent(pnode->AsParseNodeBin()->pnode1, indentAmt); pnode = pnode->AsParseNodeBin()->pnode2; } PrintPnodeWIndent(pnode, indentAmt); } } void PrintFormalsWIndent(ParseNode *pnodeArgs, int indentAmt) { for (ParseNode *pnode = pnodeArgs; pnode != nullptr; pnode = pnode->GetFormalNext()) { PrintPnodeWIndent(pnode->nop == knopParamPattern ? pnode->AsParseNodeParamPattern()->pnode1 : pnode, indentAmt); } } void PrintPnode(ParseNode *pnode) { PrintPnodeWIndent(pnode, 0); } void ParseNode::Dump() { switch (nop) { case knopFncDecl: case knopProg: LPCOLESTR name = Js::Constants::AnonymousFunction; if (this->AsParseNodeFnc()->pnodeName) { Assert(this->AsParseNodeFnc()->pnodeName->nop == knopVarDecl); name = this->AsParseNodeFnc()->pnodeName->pid->Psz(); } Output::Print(_u("%s (%d) [%d, %d]:\n"), name, this->AsParseNodeFnc()->functionId, this->AsParseNodeFnc()->lineNumber, this->AsParseNodeFnc()->columnNumber); Output::Print(_u("hasArguments: %s callsEval:%s childCallsEval:%s HasReferenceableBuiltInArguments:%s ArgumentsObjectEscapes:%s HasWith:%s HasOnlyThis:%s \n"), IsTrueOrFalse(this->AsParseNodeFnc()->HasHeapArguments()), IsTrueOrFalse(this->AsParseNodeFnc()->CallsEval()), IsTrueOrFalse(this->AsParseNodeFnc()->ChildCallsEval()), IsTrueOrFalse(this->AsParseNodeFnc()->HasReferenceableBuiltInArguments()), IsTrueOrFalse(this->AsParseNodeFnc()->GetArgumentsObjectEscapes()), IsTrueOrFalse(this->AsParseNodeFnc()->HasWithStmt()), IsTrueOrFalse(this->AsParseNodeFnc()->HasOnlyThisStmts())); if (this->AsParseNodeFnc()->funcInfo) { this->AsParseNodeFnc()->funcInfo->Dump(); } break; } } void DumpCapturedNames(ParseNodeFnc* pnodeFnc, IdentPtrSet* capturedNames, ArenaAllocator* alloc) { auto sortedNames = JsUtil::List::New(alloc); capturedNames->Map([=](const IdentPtr& pid) -> void { sortedNames->Add(pid); }); sortedNames->Sort([](void* context, const void* left, const void* right) -> int { const IdentPtr leftIdentPtr = *(const IdentPtr*)(left); const IdentPtr rightIdentPtr = *(const IdentPtr*)(right); return ::wcscmp(leftIdentPtr->Psz(), rightIdentPtr->Psz()); }, nullptr); sortedNames->Map([=](int index, const IdentPtr pid) -> void { OUTPUT_TRACE_DEBUGONLY(Js::CreateParserStatePhase, _u(" Function %u captured name \"%s\"\n"), pnodeFnc->functionId, pid->Psz()); }); } #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->GetFuncScopeId() < pnodeFnc->functionId) { iter.RemoveCurrent(); } iter.MoveNext(); } #if DBG_DUMP if (Js::Configuration::Global.flags.Trace.IsEnabled(Js::CreateParserStatePhase)) { DumpCapturedNames(pnodeFnc, capturedNames, &this->m_nodeAllocator); fflush(stdout); } #endif } } void Parser::ReleaseTemporaryGuestArena() { // In case of modules the Parser lives longer than the temporary Guest Arena. We may have already released the arena explicitly. if (!m_tempGuestArenaReleased) { // The regex patterns list has references to the temporary Guest Arena. Reset it first. m_registeredRegexPatterns.Reset(); if (this->m_scriptContext != nullptr) { this->m_scriptContext->ReleaseTemporaryGuestAllocator(m_tempGuestArena); m_tempGuestArena.Unroot(); } m_tempGuestArenaReleased = true; } } bool Parser::IsCreatingStateCache() { return (((this->m_grfscr & fscrCreateParserState) == fscrCreateParserState) && this->m_functionBody == nullptr && CONFIG_FLAG(ParserStateCache)); } void Parser::ShiftCurrDeferredStubToChildFunction(ParseNodeFnc* pnodeFnc, ParseNodeFnc* pnodeFncParent) { // Goal here is to shift the current deferred stub to point to the stubs for pnodeFnc // so we may continue parsing pnodeFnc using the correct set of stubs instead of the // stubs for pnodeFncParent. // This function assumes we are in the middle of parsing pnodeFnc which is a child // nested in pnodeFncParent. if (pnodeFnc->IsNested() && pnodeFncParent != nullptr && m_currDeferredStub != nullptr && pnodeFncParent->ichMin != pnodeFnc->ichMin) { AssertOrFailFast(pnodeFncParent->nestedCount > 0); DeferredFunctionStub* childStub = m_currDeferredStub + (pnodeFncParent->nestedCount - 1); m_currDeferredStubCount = childStub->nestedCount; m_currDeferredStub = childStub->deferredStubs; } } uint Parser::BuildDeferredStubTreeHelper(ParseNodeBlock* pnodeBlock, DeferredFunctionStub* deferredStubs, uint currentStubIndex, uint deferredStubCount, Recycler *recycler) { Assert(pnodeBlock != nullptr && (pnodeBlock->blockType == PnodeBlockType::Function || pnodeBlock->blockType == PnodeBlockType::Parameter)); ParseNodePtr pnodeChild = pnodeBlock->pnodeScopes; while (pnodeChild != nullptr) { if (pnodeChild->nop != knopFncDecl) { // We only expect to find a function body block in a parameter scope block. Assert(pnodeChild->nop == knopBlock && (pnodeBlock->blockType == PnodeBlockType::Parameter || pnodeChild->AsParseNodeBlock()->blockType == PnodeBlockType::Function)); pnodeChild = pnodeChild->AsParseNodeBlock()->pnodeNext; continue; } ParseNodeFnc* pnodeFncChild = pnodeChild->AsParseNodeFnc(); AnalysisAssertOrFailFast(currentStubIndex < deferredStubCount); Assert(pnodeFncChild->pnodeBody == nullptr); if (pnodeFncChild->IsGeneratedDefault()) { ++currentStubIndex; pnodeChild = pnodeFncChild->pnodeNext; continue; } deferredStubs[currentStubIndex].fncFlags = pnodeFncChild->fncFlags; deferredStubs[currentStubIndex].nestedCount = pnodeFncChild->nestedCount; deferredStubs[currentStubIndex].restorePoint = *pnodeFncChild->pRestorePoint; deferredStubs[currentStubIndex].deferredStubs = BuildDeferredStubTree(pnodeFncChild, recycler); deferredStubs[currentStubIndex].ichMin = pnodeChild->ichMin; // Save the set of captured names onto the deferred stub. // Since this set is allocated in the Parser arena, we'll have to convert these // into indices in a string table which will survive when the parser goes away. deferredStubs[currentStubIndex].capturedNamePointers = pnodeFncChild->GetCapturedNames(); ++currentStubIndex; pnodeChild = pnodeFncChild->pnodeNext; } return currentStubIndex; } DeferredFunctionStub * Parser::BuildDeferredStubTree(ParseNodeFnc *pnodeFnc, Recycler *recycler) { Assert(CONFIG_FLAG(ParserStateCache)); uint nestedCount = pnodeFnc->nestedCount; if (nestedCount == 0) { return nullptr; } if (pnodeFnc->deferredStub) { return pnodeFnc->deferredStub; } DeferredFunctionStub* deferredStubs = RecyclerNewArray(recycler, DeferredFunctionStub, nestedCount); uint currentStubIndex = BuildDeferredStubTreeHelper(pnodeFnc->pnodeScopes, deferredStubs, 0, nestedCount, recycler); currentStubIndex = BuildDeferredStubTreeHelper(pnodeFnc->pnodeBodyScope, deferredStubs, currentStubIndex, nestedCount, recycler); Assert(currentStubIndex == nestedCount); pnodeFnc->deferredStub = deferredStubs; return deferredStubs; }