| //------------------------------------------------------------------------------------------------------- |
| // 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 <uint32 N> |
| 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<uint32>(csz)); |
| } |
| |
| template <typename CharType> |
| IdentPtr PidHashNameLen(CharType const * psz, CharType const * end, uint32 cch); |
| template <typename CharType> |
| IdentPtr PidHashNameLen(CharType const * psz, uint32 cch); |
| template <typename CharType> |
| IdentPtr PidHashNameLenWithHash(_In_reads_(cch) CharType const * psz, CharType const * end, int32 cch, uint32 luHash); |
| |
| |
| template <typename CharType> |
| 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<typename Fn> |
| 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 |
| }; |
| |