blob: e83792110450f805950aa4449687b9e07427957d [file]
//-------------------------------------------------------------------------------------------------------
// 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
namespace Js
{
struct Utf8SourceInfo : public FinalizableObject
{
typedef JsUtil::LeafValueDictionary<Js::LocalFunctionId, Js::FunctionBody*>::Type FunctionBodyDictionary;
typedef JsUtil::LeafValueDictionary<Js::LocalFunctionId, Js::ParseableFunctionInfo*>::Type DeferredFunctionsDictionary;
typedef JsUtil::List<Js::FunctionInfo *, Recycler> FunctionInfoList;
friend class RemoteUtf8SourceInfo;
friend class ScriptContext;
public:
bool HasSource() const { return !this->sourceHolder->IsEmpty(); }
LPCUTF8 GetSource(const char16 * reason = nullptr) const;
size_t GetCbLength(const char16 * reason = nullptr) const;
ULONG GetParseFlags()
{
return this->parseFlags;
}
void SetParseFlags(ULONG parseFlags)
{
this->parseFlags = parseFlags;
}
ULONG GetByteCodeGenerationFlags()
{
return this->byteCodeGenerationFlags;
}
void SetByteCodeGenerationFlags(ULONG byteCodeGenerationFlags)
{
this->byteCodeGenerationFlags = byteCodeGenerationFlags;
}
bool IsInDebugMode() const
{
return (this->debugModeSource != nullptr || this->debugModeSourceIsEmpty) && this->m_isInDebugMode;
}
void SetInDebugMode(bool inDebugMode)
{
AssertMsg(!GetIsLibraryCode(), "Shouldn't call SetInDebugMode for Library code.");
AssertMsg(this->sourceHolder != nullptr, "We have no source holder.");
AssertMsg(this->m_isInDebugMode != inDebugMode, "Why are we setting same value");
this->m_isInDebugMode = inDebugMode;
if (!this->sourceHolder->IsDeferrable())
{
return;
}
if (inDebugMode)
{
this->debugModeSource = this->sourceHolder->GetSource(_u("Entering Debug Mode"));
this->debugModeSourceLength = this->sourceHolder->GetByteLength(_u("Entering Debug Mode"));
this->debugModeSourceIsEmpty = !this->HasSource() || this->debugModeSource == nullptr;
this->EnsureLineOffsetCache();
}
else
{
// If debugModeSourceIsEmpty is false, that means debugModeSource isn't nullptr or we aren't in debug mode.
this->debugModeSourceIsEmpty = false;
this->debugModeSource = nullptr;
this->debugModeSourceLength = 0;
}
}
void RetrieveSourceText(__out_ecount_full(cchLim - cchMin) LPOLESTR cpText, charcount_t cchMin, charcount_t cchLim) const
{
size_t cbLength = GetCbLength(_u("Utf8SourceInfo::RetrieveSourceText"));
LPCUTF8 source = GetSource(_u("Utf8SourceInfo::RetrieveSourceText"));
LPCUTF8 pbStart = nullptr;
LPCUTF8 pbEnd = nullptr;
if (cbLength == GetCchLength())
{
pbStart = source + cchMin;
pbEnd = source + cchLim;
}
else
{
pbStart = source + utf8::CharacterIndexToByteIndex(source, cbLength, cchMin, utf8::doAllowThreeByteSurrogates);
pbEnd = source + utf8::CharacterIndexToByteIndex(source, cbLength, cchLim, utf8::doAllowThreeByteSurrogates);
}
utf8::DecodeUnitsInto(cpText, pbStart, pbEnd, utf8::doAllowThreeByteSurrogates);
}
size_t CharacterIndexToByteIndex(charcount_t cchIndex) const
{
return cchIndex < m_cchLength ? (GetCbLength(_u("CharacterIndexToByteIndex")) == m_cchLength ? cchIndex : utf8::CharacterIndexToByteIndex(this->GetSource(_u("CharacterIndexToByteIndex")), GetCbLength(_u("CharacterIndexToByteIndex")), cchIndex, utf8::doAllowThreeByteSurrogates)) : m_cchLength;
}
charcount_t ByteIndexToCharacterIndex(size_t cbIndex) const
{
return cbIndex < GetCbLength(_u("CharacterIndexToByteIndex")) ? static_cast< charcount_t>(GetCbLength(_u("CharacterIndexToByteIndex")) == m_cchLength ? cbIndex : utf8::ByteIndexIntoCharacterIndex(this->GetSource(_u("CharacterIndexToByteIndex")), cbIndex, utf8::doAllowThreeByteSurrogates)) : static_cast< charcount_t >(GetCbLength(_u("CharacterIndexToByteIndex")));
}
charcount_t GetCchLength() const
{
return m_cchLength;
}
void SetCchLength(charcount_t cchLength)
{
m_cchLength = cchLength;
}
const SRCINFO* GetSrcInfo() const
{
return m_srcInfo;
}
void EnsureInitialized(int initialFunctionCount);
// The following 3 functions are on individual functions that exist within this
// source info- for them to be called, byte code generation should have created
// the function body in question, in which case, functionBodyDictionary needs to have
// been initialized
void SetFunctionBody(FunctionBody * functionBody);
void RemoveFunctionBody(FunctionBody* functionBodyBeingRemoved);
void AddTopLevelFunctionInfo(Js::FunctionInfo * functionInfo, Recycler * recycler);
void ClearTopLevelFunctionInfoList();
JsUtil::List<Js::FunctionInfo *, Recycler> * EnsureTopLevelFunctionInfoList(Recycler * recycler);
JsUtil::List<Js::FunctionInfo *, Recycler> * GetTopLevelFunctionInfoList() const
{
return this->topLevelFunctionInfoList;
}
// The following functions could get called even if EnsureInitialized hadn't gotten called
// (Namely in the OOM scenario), so we simply guard against that condition rather than
// asserting
int GetFunctionBodyCount() const
{
return (this->functionBodyDictionary ? this->functionBodyDictionary->Count() : 0);
}
// Gets a thread-unique id for this source info
// (Behaves the same as the function number)
uint GetSourceInfoId()
{
return m_sourceInfoId;
}
#if ENABLE_TTD
void SetSourceInfoForDebugReplay_TTD(uint32 newSourceInfoId)
{
this->m_sourceInfoId = newSourceInfoId;
}
#endif
void ClearFunctions()
{
if (this->functionBodyDictionary)
{
this->functionBodyDictionary->Clear();
}
}
bool HasFunctions() const
{
return (this->functionBodyDictionary ? this->functionBodyDictionary->Count() > 0 : false);
}
template <typename TDelegate>
void MapFunction(TDelegate mapper) const
{
if (this->functionBodyDictionary)
{
this->functionBodyDictionary->Map([mapper] (Js::LocalFunctionId, Js::FunctionBody* functionBody) {
Assert(functionBody);
mapper(functionBody);
});
}
}
// Get's the first function in the function body dictionary
// Used if the caller want's any function in this source info
Js::FunctionBody* GetAnyParsedFunction();
template <typename TDelegate>
void MapFunctionUntil(TDelegate mapper) const
{
if (this->functionBodyDictionary)
{
this->functionBodyDictionary->MapUntil([mapper] (Js::LocalFunctionId, Js::FunctionBody* functionBody) {
Assert(functionBody);
return mapper(functionBody);
});
}
}
Js::FunctionBody* FindFunction(Js::LocalFunctionId id) const;
template <typename TDelegate>
Js::FunctionBody* FindFunction(TDelegate predicate) const
{
Js::FunctionBody* matchedFunctionBody = nullptr;
// Function body collection could be null if we OOMed
// during byte code generation but the source info didn't get
// collected because of a false positive reference- we should
// skip over these Utf8SourceInfos.
if (this->functionBodyDictionary)
{
this->functionBodyDictionary->Map(
[&matchedFunctionBody, predicate] (Js::LocalFunctionId, Js::FunctionBody* functionBody)
{
Assert(functionBody);
if (predicate(functionBody)) {
matchedFunctionBody = functionBody;
return true;
}
return false;
});
}
return matchedFunctionBody;
}
void SetHostBuffer(BYTE * pcszCode);
bool HasDebugDocument() const
{
return m_debugDocument != nullptr;
}
void SetDebugDocument(DebugDocument * document)
{
Assert(!HasDebugDocument());
m_debugDocument = document;
}
DebugDocument* GetDebugDocument() const
{
Assert(HasDebugDocument());
return m_debugDocument;
}
void ClearDebugDocument(bool close = true);
void SetIsCesu8(bool isCesu8)
{
this->m_isCesu8 = isCesu8;
}
bool GetIsCesu8() const
{
return m_isCesu8;
}
DWORD_PTR GetSecondaryHostSourceContext() const
{
return m_secondaryHostSourceContext;
}
bool GetIsLibraryCode() const
{
return m_isLibraryCode;
}
bool GetIsXDomain() const { return m_isXDomain; }
void SetIsXDomain() { m_isXDomain = true; }
bool GetIsXDomainString() const { return m_isXDomainString; }
void SetIsXDomainString() { m_isXDomainString = true; }
DWORD_PTR GetHostSourceContext() const;
bool IsDynamic() const;
SourceContextInfo* GetSourceContextInfo() const;
void SetSecondaryHostContext(DWORD_PTR hostSourceContext);
bool IsHostManagedSource() const;
static int StaticGetHashCode(__in const Utf8SourceInfo* const si)
{
return si->GetSourceHolder()->GetHashCode();
}
static bool StaticEquals(__in Utf8SourceInfo* s1, __in Utf8SourceInfo* s2)
{
if (s1 == nullptr || s2 == nullptr) return false;
//If the source holders have the same pointer, we are expecting the sources to be equal
return (s1 == s2) || s1->GetSourceHolder()->Equals(s2->GetSourceHolder());
}
virtual void Finalize(bool isShutdown) override { /* nothing */ }
virtual void Dispose(bool isShutdown) override;
virtual void Mark(Recycler *recycler) override { AssertMsg(false, "Mark called on object that isn't TrackableObject"); }
static Utf8SourceInfo* NewWithHolder(ScriptContext* scriptContext,
ISourceHolder* sourceHolder, int32 length, SRCINFO const* srcInfo,
bool isLibraryCode, Js::Var scriptSource = nullptr);
static Utf8SourceInfo* New(ScriptContext* scriptContext, LPCUTF8 utf8String,
int32 length, size_t numBytes, SRCINFO const* srcInfo,
bool isLibraryCode);
static Utf8SourceInfo* NewWithNoCopy(ScriptContext* scriptContext,
LPCUTF8 utf8String, int32 length, size_t numBytes,
SRCINFO const* srcInfo, bool isLibraryCode, Js::Var scriptSource = nullptr);
static Utf8SourceInfo* Clone(ScriptContext* scriptContext, const Utf8SourceInfo* sourceinfo);
ScriptContext * GetScriptContext() const
{
return this->m_scriptContext;
}
void EnsureLineOffsetCache();
HRESULT EnsureLineOffsetCacheNoThrow();
void DeleteLineOffsetCache()
{
this->m_lineOffsetCache = nullptr;
}
void CreateLineOffsetCache(const JsUtil::LineOffsetCache<Recycler>::LineOffsetCacheItem *items, charcount_t numberOfItems);
size_t GetLineCount()
{
return this->GetLineOffsetCache()->GetLineCount();
}
JsUtil::LineOffsetCache<Recycler> *GetLineOffsetCache()
{
AssertMsg(this->m_lineOffsetCache != nullptr, "LineOffsetCache wasn't created, EnsureLineOffsetCache should have been called.");
return m_lineOffsetCache;
}
void GetLineInfoForCharPosition(charcount_t charPosition, charcount_t *outLineNumber, charcount_t *outColumn, charcount_t *outLineByteOffset, bool allowSlowLookup = false);
void GetCharPositionForLineInfo(charcount_t lineNumber, charcount_t *outCharPosition, charcount_t *outByteOffset)
{
Assert(outCharPosition != nullptr && outByteOffset != nullptr);
*outCharPosition = this->m_lineOffsetCache->GetCharacterOffsetForLine(lineNumber, outByteOffset);
}
void TrackDeferredFunction(Js::LocalFunctionId functionID, Js::ParseableFunctionInfo *function);
void StopTrackingDeferredFunction(Js::LocalFunctionId functionID);
template <class Fn>
void UndeferGlobalFunctions(Fn fn)
{
if (this->m_scriptContext->DoUndeferGlobalFunctions())
{
Assert(m_deferredFunctionsInitialized);
if (m_deferredFunctionsDictionary == nullptr)
{
return;
}
DeferredFunctionsDictionary *tmp = this->m_deferredFunctionsDictionary;
this->m_deferredFunctionsDictionary = nullptr;
tmp->MapAndRemoveIf(fn);
}
}
ISourceHolder* GetSourceHolder() const
{
return sourceHolder;
}
bool IsCesu8() const
{
return this->m_isCesu8;
}
void SetCallerUtf8SourceInfo(Utf8SourceInfo* callerUtf8SourceInfo);
Utf8SourceInfo* GetCallerUtf8SourceInfo() const;
bool GetDebugDocumentName(BSTR * sourceName);
private:
Field(charcount_t) m_cchLength; // The number of characters encoded in m_utf8Source.
Field(ISourceHolder*) sourceHolder;
FieldNoBarrier(BYTE*) m_pHostBuffer; // Pointer to a host source buffer (null unless this is host code that we need to free)
Field(FunctionBodyDictionary*) functionBodyDictionary;
Field(DeferredFunctionsDictionary*) m_deferredFunctionsDictionary;
Field(FunctionInfoList*) topLevelFunctionInfoList;
Field(DebugDocument*) m_debugDocument;
Field(const SRCINFO*) m_srcInfo;
Field(DWORD_PTR) m_secondaryHostSourceContext;
Field(LPCUTF8) debugModeSource;
Field(size_t) debugModeSourceLength;
Field(ScriptContext* const) m_scriptContext; // Pointer to ScriptContext under which this source info was created
// Line offset cache used for quickly finding line/column offsets.
Field(JsUtil::LineOffsetCache<Recycler>*) m_lineOffsetCache;
// Utf8SourceInfo of the caller, used for mapping eval/new Function node to its caller node for debugger
Field(Utf8SourceInfo*) callerUtf8SourceInfo;
Field(bool) m_deferredFunctionsInitialized : 1;
Field(bool) m_isCesu8 : 1;
Field(bool) m_hasHostBuffer : 1;
Field(bool) m_isLibraryCode : 1; // true, the current source belongs to the internal library code. Used for debug purpose to not show in debugger
Field(bool) m_isXDomain : 1;
// we found that m_isXDomain could cause regression without CORS, so the new flag is just for callee.caller in window.onerror
Field(bool) m_isXDomainString : 1;
Field(bool) debugModeSourceIsEmpty : 1;
Field(bool) m_isInDebugMode : 1;
Field(uint) m_sourceInfoId;
// Various flags preserved for Edit-and-Continue re-compile purpose
Field(ULONG) parseFlags;
Field(ULONG) byteCodeGenerationFlags;
Utf8SourceInfo(ISourceHolder *sourceHolder, int32 cchLength, SRCINFO const* srcInfo,
DWORD_PTR secondaryHostSourceContext, ScriptContext* scriptContext,
bool isLibraryCode, Js::Var scriptSource = nullptr);
#ifndef NTBUILD
Field(Js::Var) sourceRef; // keep source string reference to prevent GC
#endif
};
}
template <>
struct DefaultComparer<Js::Utf8SourceInfo*>
{
inline static bool Equals(Js::Utf8SourceInfo* const& x, Js::Utf8SourceInfo* const& y)
{
return Js::Utf8SourceInfo::StaticEquals(x, y);
}
inline static hash_t GetHashCode(Js::Utf8SourceInfo* const& s)
{
return Js::Utf8SourceInfo::StaticGetHashCode(s);
}
};