//------------------------------------------------------------------------------------------------------- // Copyright (C) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. //------------------------------------------------------------------------------------------------------- #pragma once // StaticSym contains a string literal at the end (flexible array) and is // meant to be initialized statically. However, flexible array initialization // is not allowed in standard C++. We declare each StaticSym with length // instead and cast to common StaticSymLen<0>* (StaticSym*) to access. template struct StaticSymLen { uint32 luHash; uint32 cch; OLECHAR sz[N]; }; typedef StaticSymLen<0> StaticSym; /*************************************************************************** Hashing functions. Definitions in core\hashfunc.cpp. ***************************************************************************/ ULONG CaseSensitiveComputeHash(LPCOLESTR prgch, LPCOLESTR end); ULONG CaseSensitiveComputeHash(LPCUTF8 prgch, LPCUTF8 end); ULONG CaseInsensitiveComputeHash(LPCOLESTR posz); enum { fidNil = 0x0000, fidKwdRsvd = 0x0001, // the keyword is a reserved word fidKwdFutRsvd = 0x0002, // a future reserved word, but only in strict mode fidModuleExport = 0x8000 // name is module export }; struct BlockIdsStack { int id; BlockIdsStack *prev; }; class Span { charcount_t m_ichMin; charcount_t m_ichLim; public: Span(): m_ichMin((charcount_t)-1), m_ichLim((charcount_t)-1) { } Span(charcount_t ichMin, charcount_t ichLim): m_ichMin(ichMin), m_ichLim(ichLim) { } charcount_t GetIchMin() { return m_ichMin; } charcount_t GetIchLim() { Assert(m_ichMin != (charcount_t)-1); return m_ichLim; } void Set(charcount_t ichMin, charcount_t ichLim) { m_ichMin = ichMin; m_ichLim = ichLim; } operator bool() { return m_ichMin != -1; } }; struct PidRefStack { PidRefStack() : isAsg(false), isDynamic(false), id(0), funcId(0), sym(nullptr), prev(nullptr), isEscape(false), isModuleExport(false), isFuncAssignment(false), isUsedInLdElem(false) {} PidRefStack(int id, Js::LocalFunctionId funcId) : isAsg(false), isDynamic(false), id(id), funcId(funcId), sym(nullptr), prev(nullptr), isEscape(false), isModuleExport(false), isFuncAssignment(false), isUsedInLdElem(false) {} int GetScopeId() const { return id; } Js::LocalFunctionId GetFuncScopeId() const { return funcId; } Symbol *GetSym() const { return sym; } void SetSym(Symbol *sym) { this->sym = sym; } bool IsAssignment() const { return isAsg; } bool IsFuncAssignment() const { return isFuncAssignment; } bool IsEscape() const { return isEscape; } void SetIsEscape(bool is) { isEscape = is; } bool IsDynamicBinding() const { return isDynamic; } void SetDynamicBinding() { isDynamic = true; } bool IsUsedInLdElem() const { return isUsedInLdElem; } void SetIsUsedInLdElem(bool is) { isUsedInLdElem = is; } Symbol **GetSymRef() { return &sym; } bool isAsg; bool isDynamic; bool isModuleExport; bool isEscape; bool isFuncAssignment; bool isUsedInLdElem; int id; Js::LocalFunctionId funcId; Symbol *sym; PidRefStack *prev; }; enum AssignmentState : byte { NotAssigned, AssignedOnce, AssignedMultipleTimes }; struct Ident { friend class HashTbl; private: Ident * m_pidNext; // next identifier in this hash bucket PidRefStack *m_pidRefStack; ushort m_tk; // token# if identifier is a keyword ushort m_grfid; // see fidXXX above uint32 m_luHash; // hash value uint32 m_cch; // length of the identifier spelling Js::PropertyId m_propertyId; AssignmentState assignmentState; bool isUsedInLdElem; OLECHAR m_sz[]; // the spelling follows (null terminated) void SetTk(tokens tk, ushort grfid); public: LPCOLESTR Psz(void) { return m_sz; } uint32 Cch(void) { return m_cch; } tokens Tk(bool isStrictMode); uint32 Hash(void) { return m_luHash; } PidRefStack *GetTopRef() const { return m_pidRefStack; } PidRefStack *GetTopRef(uint maxBlockId) const { PidRefStack *ref; for (ref = m_pidRefStack; ref && (uint)ref->id > maxBlockId; ref = ref->prev) { ; // nothing } return ref; } void SetTopRef(PidRefStack *ref) { m_pidRefStack = ref; } void PromoteAssignmentState() { if (assignmentState == NotAssigned) { assignmentState = AssignedOnce; } else if (assignmentState == AssignedOnce) { assignmentState = AssignedMultipleTimes; } } bool IsUsedInLdElem() const { return this->isUsedInLdElem; } void SetIsUsedInLdElem(bool is) { this->isUsedInLdElem = is; } static void TrySetIsUsedInLdElem(ParseNode * pnode); bool IsSingleAssignment() { return assignmentState == AssignedOnce; } PidRefStack *GetPidRefForScopeId(int scopeId) { PidRefStack *ref; for (ref = m_pidRefStack; ref; ref = ref->prev) { int refId = ref->GetScopeId(); if (refId == scopeId) { return ref; } if (refId < scopeId) { break; } } return nullptr; } void PushPidRef(int blockId, Js::LocalFunctionId funcId, PidRefStack *newRef) { AssertMsg(blockId >= 0, "Block Id's should be greater than 0"); newRef->id = blockId; newRef->funcId = funcId; newRef->prev = m_pidRefStack; m_pidRefStack = newRef; } PidRefStack * RemovePrevPidRef(PidRefStack *ref) { PidRefStack *prevRef; if (ref == nullptr) { prevRef = m_pidRefStack; Assert(prevRef); m_pidRefStack = prevRef->prev; } else { prevRef = ref->prev; Assert(prevRef); ref->prev = prevRef->prev; } return prevRef; } PidRefStack * FindOrAddPidRef(ArenaAllocator *alloc, int scopeId, Js::LocalFunctionId funcId) { // If the stack is empty, or we are pushing to the innermost scope already, // we can go ahead and push a new PidRef on the stack. if (m_pidRefStack == nullptr) { PidRefStack *newRef = Anew(alloc, PidRefStack, scopeId, funcId); if (newRef == nullptr) { return nullptr; } newRef->prev = m_pidRefStack; m_pidRefStack = newRef; return newRef; } // Search for the corresponding PidRef, or the position to insert the new PidRef. PidRefStack *ref = m_pidRefStack; PidRefStack *prevRef = nullptr; while (1) { // We may already have a ref for this scopeId. if (ref->id == scopeId) { return ref; } if (ref->prev == nullptr || ref->id < scopeId) { // No existing PidRef for this scopeId, so create and insert one at this position. PidRefStack *newRef = Anew(alloc, PidRefStack, scopeId, funcId); if (newRef == nullptr) { return nullptr; } if (ref->id < scopeId) { if (prevRef != nullptr) { // Param scope has a reference to the same pid as the one we are inserting into the body. // There is a another reference (prevRef), probably from an inner block in the body. // So we should insert the new reference between them. newRef->prev = prevRef->prev; prevRef->prev = newRef; } else { // When we have code like below, prevRef will be null, // function (a = x) { var x = 1; } newRef->prev = m_pidRefStack; m_pidRefStack = newRef; } } else { newRef->prev = ref->prev; ref->prev = newRef; } return newRef; } prevRef = ref; ref = ref->prev; } } Js::PropertyId GetPropertyId() const { return m_propertyId; } void SetPropertyId(Js::PropertyId id) { m_propertyId = id; } void SetIsModuleExport() { m_grfid |= fidModuleExport; } BOOL GetIsModuleExport() const { return m_grfid & fidModuleExport; } static tokens TkFromNameLen(uint32 luHash, _In_reads_(cch) LPCOLESTR prgch, uint32 cch, bool isStrictMode, ushort * pgrfid, ushort * ptk); #if DBG static tokens TkFromNameLen(_In_reads_(cch) LPCOLESTR prgch, uint32 cch, bool isStrictMode); #endif }; /*****************************************************************************/ class HashTbl { public: HashTbl(uint cidHash = DEFAULT_HASH_TABLE_SIZE) { AssertCanHandleOutOfMemory(); m_prgpidName = nullptr; memset(&m_rpid, 0, sizeof(m_rpid)); if (!Init(cidHash)) { Js::Throw::OutOfMemory(); } } ~HashTbl(void) {} BOOL TokIsBinop(tokens tk, int *popl, OpCode *pnop) { const KWD *pkwd = KwdOfTok(tk); if (nullptr == pkwd) return FALSE; *popl = pkwd->prec2; *pnop = pkwd->nop2; return TRUE; } BOOL TokIsUnop(tokens tk, int *popl, OpCode *pnop) { const KWD *pkwd = KwdOfTok(tk); if (nullptr == pkwd) return FALSE; *popl = pkwd->prec1; *pnop = pkwd->nop1; return TRUE; } IdentPtr PidFromTk(tokens tk); IdentPtr PidHashName(LPCOLESTR psz) { size_t csz = wcslen(psz); Assert(csz <= ULONG_MAX); return PidHashNameLen(psz, static_cast(csz)); } template IdentPtr PidHashNameLen(CharType const * psz, CharType const * end, uint32 cch); template IdentPtr PidHashNameLen(CharType const * psz, uint32 cch); template IdentPtr PidHashNameLenWithHash(_In_reads_(cch) CharType const * psz, CharType const * end, int32 cch, uint32 luHash); template inline IdentPtr FindExistingPid( CharType const * prgch, CharType const * end, int32 cch, uint32 luHash, IdentPtr **pppInsert, int32 *pBucketCount #if PROFILE_DICTIONARY , int& depth #endif ); NoReleaseAllocator* GetAllocator() {return &m_noReleaseAllocator;} bool Contains(_In_reads_(cch) LPCOLESTR prgch, int32 cch); template void VisitPids(Fn fn) { for (uint i = 0; i <= m_luMask; i++) { for (IdentPtr pid = m_prgpidName[i]; pid; pid = pid->m_pidNext) { fn(pid); } } } private: static const uint DEFAULT_HASH_TABLE_SIZE = 256; NoReleaseAllocator m_noReleaseAllocator; // to allocate identifiers Ident ** m_prgpidName; // hash table for names uint32 m_luMask; // hash mask uint32 m_luCount; // count of the number of entires in the hash table IdentPtr m_rpid[tkLimKwd]; // Called to grow the number of buckets in the table to reduce the table density. void Grow(); // Automatically grow the table if a bucket's length grows beyond BucketLengthLimit and the table is densely populated. static const uint BucketLengthLimit = 5; // When growing the bucket size we'll grow by GrowFactor. GrowFactor MUST be a power of 2. static const uint GrowFactor = 4; #if DEBUG uint CountAndVerifyItems(IdentPtr *buckets, uint bucketCount, uint mask); #endif static bool CharsAreEqual(__in_z LPCOLESTR psz1, __in_ecount(psz2end - psz2) LPCOLESTR psz2, LPCOLESTR psz2end) { return memcmp(psz1, psz2, (psz2end - psz2) * sizeof(OLECHAR)) == 0; } static bool CharsAreEqual(__in_z LPCOLESTR psz1, LPCUTF8 psz2, LPCUTF8 psz2end) { return utf8::CharsAreEqual(psz1, psz2, psz2end, utf8::doAllowThreeByteSurrogates); } static bool CharsAreEqual(__in_z LPCOLESTR psz1, __in_ecount(psz2end - psz2) char const * psz2, char const * psz2end) { while (psz2 < psz2end) { if (*psz1++ != *psz2++) { return false; } } return true; } static void CopyString(__in_ecount((psz2end - psz2) + 1) LPOLESTR psz1, __in_ecount(psz2end - psz2) LPCOLESTR psz2, LPCOLESTR psz2end) { size_t cch = psz2end - psz2; js_memcpy_s(psz1, cch * sizeof(OLECHAR), psz2, cch * sizeof(OLECHAR)); psz1[cch] = 0; } static void CopyString(LPOLESTR psz1, LPCUTF8 psz2, LPCUTF8 psz2end) { utf8::DecodeUnitsIntoAndNullTerminate(psz1, psz2, psz2end); } static void CopyString(LPOLESTR psz1, char const * psz2, char const * psz2end) { while (psz2 < psz2end) { *(psz1++) = *psz2++; } *psz1 = 0; } // note: on failure this may throw or return FALSE, depending on // where the failure occurred. BOOL Init(uint cidHash); /*************************************************************************/ /* The following members are related to the keyword descriptor tables */ /*************************************************************************/ struct KWD { OpCode nop2; byte prec2; OpCode nop1; byte prec1; }; struct ReservedWordInfo { StaticSym const * sym; ushort grfid; }; static const ReservedWordInfo s_reservedWordInfo[tkID]; static const KWD g_mptkkwd[tkLimKwd]; static const KWD * KwdOfTok(tokens tk) { return (unsigned int)tk < tkLimKwd ? g_mptkkwd + tk : nullptr; } static __declspec(noreturn) void OutOfMemory(); #if PROFILE_DICTIONARY DictionaryStats *stats; #endif };