blob: 6b8754820f71fe4fc2c602fcfe347ff35535800b [file] [log] [blame]
//-------------------------------------------------------------------------------------------------------
// 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
};