blob: b62688f56fbf4a41d10905981e27403a0ed3c6fc [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "RuntimeDebugPch.h"
#if ENABLE_TTD
namespace TTD
{
void ThreadContextTTD::AddNewScriptContext_Helper(Js::ScriptContext* ctx, HostScriptContextCallbackFunctor& callbackFunctor, bool noNative, bool debugMode)
{
////
//First just setup the standard things needed for a script context
ctx->TTDHostCallbackFunctor = callbackFunctor;
if(noNative)
{
ctx->ForceNoNative();
}
if(debugMode)
{
#ifdef _WIN32
ctx->InitializeDebugging();
#else
//
//TODO: x-plat does not like some parts of initiallize debugging so just set the flag we need
//
ctx->GetDebugContext()->SetDebuggerMode(Js::DebuggerMode::Debugging);
#endif
}
ctx->InitializeCoreImage_TTD();
TTDAssert(!this->m_contextList.Contains(ctx), "We should only be adding at creation time!!!");
this->m_contextList.Add(ctx);
}
void ThreadContextTTD::CleanRecordWeakRootMap()
{
this->m_ttdRecordRootWeakMap->Map([&](Js::RecyclableObject* key, bool, const RecyclerWeakReference<Js::RecyclableObject>*)
{
; //nop just map to force the clean
});
}
ThreadContextTTD::ThreadContextTTD(ThreadContext* threadContext, void* runtimeHandle, uint32 snapInterval, uint32 snapHistoryLength)
: m_threadCtx(threadContext), m_runtimeHandle(runtimeHandle), m_contextCreatedOrDestoyedInReplay(false),
SnapInterval(snapInterval), SnapHistoryLength(snapHistoryLength),
m_activeContext(nullptr), m_contextList(&HeapAllocator::Instance), m_ttdContextToExternalRefMap(&HeapAllocator::Instance),
m_ttdRootTagToObjectMap(&HeapAllocator::Instance), m_ttdMayBeLongLivedRoot(&HeapAllocator::Instance),
m_ttdRecordRootWeakMap(),m_ttdReplayRootPinSet(),
TTDataIOInfo({ 0 }), TTDExternalObjectFunctions({ 0 })
{
Recycler* tctxRecycler = this->m_threadCtx->GetRecycler();
this->m_ttdRecordRootWeakMap.Root(RecyclerNew(tctxRecycler, RecordRootMap, tctxRecycler), tctxRecycler);
this->m_ttdReplayRootPinSet.Root(RecyclerNew(tctxRecycler, ObjectPinSet, tctxRecycler), tctxRecycler);
}
ThreadContextTTD::~ThreadContextTTD()
{
for(auto iter = this->m_ttdContextToExternalRefMap.GetIterator(); iter.IsValid(); iter.MoveNext())
{
this->m_threadCtx->GetRecycler()->RootRelease(iter.CurrentValue());
}
this->m_ttdContextToExternalRefMap.Clear();
this->m_activeContext = nullptr;
this->m_contextList.Clear();
this->m_ttdRootTagToObjectMap.Clear();
this->m_ttdMayBeLongLivedRoot.Clear();
if(this->m_ttdRecordRootWeakMap != nullptr)
{
this->m_ttdRecordRootWeakMap.Unroot(this->m_threadCtx->GetRecycler());
}
if(this->m_ttdReplayRootPinSet != nullptr)
{
this->m_ttdReplayRootPinSet.Unroot(this->m_threadCtx->GetRecycler());
}
}
ThreadContext* ThreadContextTTD::GetThreadContext()
{
return this->m_threadCtx;
}
void* ThreadContextTTD::GetRuntimeHandle()
{
return this->m_runtimeHandle;
}
FinalizableObject* ThreadContextTTD::GetRuntimeContextForScriptContext(Js::ScriptContext* ctx)
{
return this->m_ttdContextToExternalRefMap.Lookup(ctx, nullptr);
}
bool ThreadContextTTD::ContextCreatedOrDestoyedInReplay() const
{
return this->m_contextCreatedOrDestoyedInReplay;
}
void ThreadContextTTD::ResetContextCreatedOrDestoyedInReplay()
{
this->m_contextCreatedOrDestoyedInReplay = false;
}
const JsUtil::List<Js::ScriptContext*, HeapAllocator>& ThreadContextTTD::GetTTDContexts() const
{
return this->m_contextList;
}
void ThreadContextTTD::AddNewScriptContextRecord(FinalizableObject* externalCtx, Js::ScriptContext* ctx, HostScriptContextCallbackFunctor& callbackFunctor, bool noNative, bool debugMode)
{
this->AddNewScriptContext_Helper(ctx, callbackFunctor, noNative, debugMode);
this->AddRootRef_Record(TTD_CONVERT_OBJ_TO_LOG_PTR_ID(ctx->GetGlobalObject()), ctx->GetGlobalObject());
ctx->ScriptContextLogTag = TTD_CONVERT_OBJ_TO_LOG_PTR_ID(ctx->GetGlobalObject());
this->AddRootRef_Record(TTD_CONVERT_OBJ_TO_LOG_PTR_ID(ctx->GetLibrary()->GetUndefined()), ctx->GetLibrary()->GetUndefined());
this->AddRootRef_Record(TTD_CONVERT_OBJ_TO_LOG_PTR_ID(ctx->GetLibrary()->GetNull()), ctx->GetLibrary()->GetNull());
this->AddRootRef_Record(TTD_CONVERT_OBJ_TO_LOG_PTR_ID(ctx->GetLibrary()->GetTrue()), ctx->GetLibrary()->GetTrue());
this->AddRootRef_Record(TTD_CONVERT_OBJ_TO_LOG_PTR_ID(ctx->GetLibrary()->GetFalse()), ctx->GetLibrary()->GetFalse());
}
void ThreadContextTTD::AddNewScriptContextReplay(FinalizableObject* externalCtx, Js::ScriptContext* ctx, HostScriptContextCallbackFunctor& callbackFunctor, bool noNative, bool debugMode)
{
this->AddNewScriptContext_Helper(ctx, callbackFunctor, noNative, debugMode);
this->m_contextCreatedOrDestoyedInReplay = true;
this->m_threadCtx->GetRecycler()->RootAddRef(externalCtx);
this->m_ttdContextToExternalRefMap.Add(ctx, externalCtx);
}
void ThreadContextTTD::SetActiveScriptContext(Js::ScriptContext* ctx)
{
TTDAssert(ctx == nullptr || this->m_contextList.Contains(ctx), "Missing value!!!");
this->m_activeContext = ctx;
}
Js::ScriptContext* ThreadContextTTD::GetActiveScriptContext()
{
return this->m_activeContext;
}
void ThreadContextTTD::NotifyCtxDestroyInRecord(Js::ScriptContext* ctx)
{
if(this->m_contextList.Contains(ctx))
{
this->m_contextList.Remove(ctx);
}
}
void ThreadContextTTD::ClearContextsForSnapRestore(JsUtil::List<FinalizableObject*, HeapAllocator>& deadCtxs)
{
for(int32 i = 0; i < this->m_contextList.Count(); ++i)
{
Js::ScriptContext* ctx = this->m_contextList.Item(i);
FinalizableObject* externalCtx = this->m_ttdContextToExternalRefMap.Item(ctx);
deadCtxs.Add(externalCtx);
}
this->m_ttdContextToExternalRefMap.Clear();
this->m_contextList.Clear();
this->m_activeContext = nullptr;
}
void ThreadContextTTD::ClearLocalRootsAndRefreshMap_Replay()
{
JsUtil::BaseHashSet<Js::RecyclableObject*, HeapAllocator> image(&HeapAllocator::Instance);
this->m_ttdRootTagToObjectMap.MapAndRemoveIf([&](JsUtil::SimpleDictionaryEntry<TTD_LOG_PTR_ID, Js::RecyclableObject*>& entry) -> bool
{
bool localonly = !this->m_ttdMayBeLongLivedRoot.LookupWithKey(entry.Key(), false);
if(!localonly)
{
image.AddNew(entry.Value());
}
return localonly;
});
this->m_ttdReplayRootPinSet->MapAndRemoveIf([&](Js::RecyclableObject* value) -> bool
{
return !image.Contains(value);
});
}
void ThreadContextTTD::LoadInvertedRootMap(JsUtil::BaseDictionary<Js::RecyclableObject*, TTD_LOG_PTR_ID, HeapAllocator>& objToLogIdMap) const
{
for(auto iter = this->m_ttdRootTagToObjectMap.GetIterator(); iter.IsValid(); iter.MoveNext())
{
objToLogIdMap.AddNew(iter.CurrentValue(), iter.CurrentKey());
}
}
Js::RecyclableObject* ThreadContextTTD::LookupObjectForLogID(TTD_LOG_PTR_ID origId)
{
//Local root always has mappings for all the ids
return this->m_ttdRootTagToObjectMap.Item(origId);
}
void ThreadContextTTD::ClearRootsForSnapRestore()
{
this->m_ttdRootTagToObjectMap.Clear();
this->m_ttdMayBeLongLivedRoot.Clear();
this->m_ttdReplayRootPinSet->Clear();
}
void ThreadContextTTD::ForceSetRootInfoInRestore(TTD_LOG_PTR_ID logid, Js::RecyclableObject* obj, bool maybeLongLived)
{
this->m_ttdRootTagToObjectMap.Item(logid, obj);
if(maybeLongLived)
{
this->m_ttdMayBeLongLivedRoot.Item(logid, maybeLongLived);
}
this->m_ttdReplayRootPinSet->AddNew(obj);
}
void ThreadContextTTD::SyncRootsBeforeSnapshot_Record()
{
this->CleanRecordWeakRootMap();
this->m_ttdRootTagToObjectMap.MapAndRemoveIf([&](JsUtil::SimpleDictionaryEntry<TTD_LOG_PTR_ID, Js::RecyclableObject*>& entry) -> bool
{
return !this->m_ttdRecordRootWeakMap->Lookup(entry.Value(), false);
});
this->m_ttdMayBeLongLivedRoot.MapAndRemoveIf([&](JsUtil::SimpleDictionaryEntry<TTD_LOG_PTR_ID, bool>& entry) -> bool
{
return !this->m_ttdRootTagToObjectMap.ContainsKey(entry.Key());
});
}
void ThreadContextTTD::SyncCtxtsAndRootsWithSnapshot_Replay(uint32 liveContextCount, TTD_LOG_PTR_ID* liveContextIdArray, uint32 liveRootCount, TTD_LOG_PTR_ID* liveRootIdArray)
{
//First sync up the context list -- releasing any contexts that are not also there in our initial recording
JsUtil::BaseHashSet<TTD_LOG_PTR_ID, HeapAllocator> lctxSet(&HeapAllocator::Instance);
for(uint32 i = 0; i < liveContextCount; ++i)
{
lctxSet.AddNew(liveContextIdArray[i]);
}
int32 ctxpos = 0;
while(ctxpos < this->m_contextList.Count())
{
Js::ScriptContext* ctx = this->m_contextList.Item(ctxpos);
if(lctxSet.Contains(ctx->ScriptContextLogTag))
{
ctxpos++;
}
else
{
this->m_contextCreatedOrDestoyedInReplay = true;
this->m_contextList.Remove(ctx);
FinalizableObject* externalCtx = this->m_ttdContextToExternalRefMap.Item(ctx);
this->m_ttdContextToExternalRefMap.Remove(ctx);
this->m_threadCtx->GetRecycler()->RootRelease(externalCtx);
//no inc of ctxpos since list go shorter!!!
}
}
//Now sync up the root list wrt. long lived roots that we recorded
JsUtil::BaseHashSet<TTD_LOG_PTR_ID, HeapAllocator> refInfoMap(&HeapAllocator::Instance);
for(uint32 i = 0; i < liveRootCount; ++i)
{
refInfoMap.AddNew(liveRootIdArray[i]);
}
this->m_ttdMayBeLongLivedRoot.MapAndRemoveIf([&](JsUtil::SimpleDictionaryEntry<TTD_LOG_PTR_ID, bool>& entry) -> bool
{
return !refInfoMap.Contains(entry.Key());
});
this->ClearLocalRootsAndRefreshMap_Replay();
}
Js::ScriptContext* ThreadContextTTD::LookupContextForScriptId(TTD_LOG_PTR_ID ctxId) const
{
for(int i = 0; i < this->m_contextList.Count(); ++i)
{
if(this->m_contextList.Item(i)->ScriptContextLogTag == ctxId)
{
return this->m_contextList.Item(i);
}
}
return nullptr;
}
ScriptContextTTD::ScriptContextTTD(Js::ScriptContext* ctx)
: m_ctx(ctx),
m_ttdPendingAsyncModList(&HeapAllocator::Instance),
m_ttdTopLevelScriptLoad(&HeapAllocator::Instance), m_ttdTopLevelNewFunction(&HeapAllocator::Instance), m_ttdTopLevelEval(&HeapAllocator::Instance),
m_ttdFunctionBodyParentMap(&HeapAllocator::Instance)
{
Recycler* ctxRecycler = this->m_ctx->GetRecycler();
this->m_ttdPinnedRootFunctionSet.Root(RecyclerNew(ctxRecycler, TTD::FunctionBodyPinSet, ctxRecycler), ctxRecycler);
this->TTDWeakReferencePinSet.Root(RecyclerNew(ctxRecycler, TTD::ObjectPinSet, ctxRecycler), ctxRecycler);
}
ScriptContextTTD::~ScriptContextTTD()
{
this->m_ttdPendingAsyncModList.Clear();
this->m_ttdTopLevelScriptLoad.Clear();
this->m_ttdTopLevelNewFunction.Clear();
this->m_ttdTopLevelEval.Clear();
if(this->m_ttdPinnedRootFunctionSet != nullptr)
{
this->m_ttdPinnedRootFunctionSet.Unroot(this->m_ttdPinnedRootFunctionSet->GetAllocator());
}
this->m_ttdFunctionBodyParentMap.Clear();
if(this->TTDWeakReferencePinSet != nullptr)
{
this->TTDWeakReferencePinSet.Unroot(this->TTDWeakReferencePinSet->GetAllocator());
}
}
void ScriptContextTTD::AddToAsyncPendingList(Js::ArrayBuffer* trgt, uint32 index)
{
TTDPendingAsyncBufferModification pending = { trgt, index };
this->m_ttdPendingAsyncModList.Add(pending);
}
void ScriptContextTTD::GetFromAsyncPendingList(TTDPendingAsyncBufferModification* pendingInfo, byte* finalModPos)
{
pendingInfo->ArrayBufferVar = nullptr;
pendingInfo->Index = 0;
const byte* currentBegin = nullptr;
int32 pos = -1;
for(int32 i = 0; i < this->m_ttdPendingAsyncModList.Count(); ++i)
{
const TTDPendingAsyncBufferModification& pi = this->m_ttdPendingAsyncModList.Item(i);
const Js::ArrayBuffer* pbuff = Js::ArrayBuffer::FromVar(pi.ArrayBufferVar);
const byte* pbuffBegin = pbuff->GetBuffer() + pi.Index;
const byte* pbuffMax = pbuff->GetBuffer() + pbuff->GetByteLength();
//if the final mod is less than the start of this buffer + index or off then end then this definitely isn't it so skip
if(pbuffBegin > finalModPos || pbuffMax < finalModPos)
{
continue;
}
//it is in the right range so now we assume non-overlapping so we see if this pbuffBegin is closer than the current best
TTDAssert(finalModPos != currentBegin, "We have something strange!!!");
if(currentBegin == nullptr || finalModPos < currentBegin)
{
currentBegin = pbuffBegin;
pos = (int32)i;
}
}
TTDAssert(pos != -1, "Missing matching register!!!");
*pendingInfo = this->m_ttdPendingAsyncModList.Item(pos);
this->m_ttdPendingAsyncModList.RemoveAt(pos);
}
const JsUtil::List<TTDPendingAsyncBufferModification, HeapAllocator>& ScriptContextTTD::GetPendingAsyncModListForSnapshot() const
{
return this->m_ttdPendingAsyncModList;
}
void ScriptContextTTD::ClearPendingAsyncModListForSnapRestore()
{
this->m_ttdPendingAsyncModList.Clear();
}
void ScriptContextTTD::GetLoadedSources(JsUtil::List<TTD::TopLevelFunctionInContextRelation, HeapAllocator>& topLevelScriptLoad, JsUtil::List<TTD::TopLevelFunctionInContextRelation, HeapAllocator>& topLevelNewFunction, JsUtil::List<TTD::TopLevelFunctionInContextRelation, HeapAllocator>& topLevelEval)
{
TTDAssert(topLevelScriptLoad.Count() == 0 && topLevelNewFunction.Count() == 0 && topLevelEval.Count() == 0, "Should be empty when you call this.");
topLevelScriptLoad.AddRange(this->m_ttdTopLevelScriptLoad);
topLevelNewFunction.AddRange(this->m_ttdTopLevelNewFunction);
topLevelEval.AddRange(this->m_ttdTopLevelEval);
}
bool ScriptContextTTD::IsBodyAlreadyLoadedAtTopLevel(Js::FunctionBody* body) const
{
return this->m_ttdPinnedRootFunctionSet->Contains(body);
}
void ScriptContextTTD::ProcessFunctionBodyOnLoad(Js::FunctionBody* body, Js::FunctionBody* parent)
{
//if this is a root (parent is null) then put this in the rootbody pin set so it isn't reclaimed on us
if(parent == nullptr)
{
TTDAssert(!this->m_ttdPinnedRootFunctionSet->Contains(body), "We already added this function!!!");
this->m_ttdPinnedRootFunctionSet->AddNew(body);
}
this->m_ttdFunctionBodyParentMap.AddNew(body, parent);
for(uint32 i = 0; i < body->GetNestedCount(); ++i)
{
Js::ParseableFunctionInfo* pfiMid = body->GetNestedFunctionForExecution(i);
Js::FunctionBody* currfb = TTD::JsSupport::ForceAndGetFunctionBody(pfiMid);
this->ProcessFunctionBodyOnLoad(currfb, body);
}
}
void ScriptContextTTD::RegisterLoadedScript(Js::FunctionBody* body, uint32 bodyCtrId)
{
TTD::TopLevelFunctionInContextRelation relation;
relation.TopLevelBodyCtr = bodyCtrId;
relation.ContextSpecificBodyPtrId = TTD_CONVERT_FUNCTIONBODY_TO_PTR_ID(body);
this->m_ttdTopLevelScriptLoad.Add(relation);
}
void ScriptContextTTD::RegisterNewScript(Js::FunctionBody* body, uint32 bodyCtrId)
{
TTD::TopLevelFunctionInContextRelation relation;
relation.TopLevelBodyCtr = bodyCtrId;
relation.ContextSpecificBodyPtrId = TTD_CONVERT_FUNCTIONBODY_TO_PTR_ID(body);
this->m_ttdTopLevelNewFunction.Add(relation);
}
void ScriptContextTTD::RegisterEvalScript(Js::FunctionBody* body, uint32 bodyCtrId)
{
TTD::TopLevelFunctionInContextRelation relation;
relation.TopLevelBodyCtr = bodyCtrId;
relation.ContextSpecificBodyPtrId = TTD_CONVERT_FUNCTIONBODY_TO_PTR_ID(body);
this->m_ttdTopLevelEval.Add(relation);
}
Js::FunctionBody* ScriptContextTTD::ResolveParentBody(Js::FunctionBody* body) const
{
//Want to return null if this has no parent (or this is an internal function and we don't care about parents)
return this->m_ttdFunctionBodyParentMap.LookupWithKey(body, nullptr);
}
uint32 ScriptContextTTD::FindTopLevelCtrForBody(Js::FunctionBody* body) const
{
Js::FunctionBody* rootBody = body;
while(this->ResolveParentBody(rootBody) != nullptr)
{
rootBody = this->ResolveParentBody(rootBody);
}
TTD_PTR_ID trgtid = TTD_CONVERT_FUNCTIONBODY_TO_PTR_ID(rootBody);
for(int32 i = 0; i < this->m_ttdTopLevelScriptLoad.Count(); ++i)
{
if(this->m_ttdTopLevelScriptLoad.Item(i).ContextSpecificBodyPtrId == trgtid)
{
return this->m_ttdTopLevelScriptLoad.Item(i).TopLevelBodyCtr;
}
}
for(int32 i = 0; i < this->m_ttdTopLevelNewFunction.Count(); ++i)
{
if(this->m_ttdTopLevelNewFunction.Item(i).ContextSpecificBodyPtrId == trgtid)
{
return this->m_ttdTopLevelNewFunction.Item(i).TopLevelBodyCtr;
}
}
for(int32 i = 0; i < this->m_ttdTopLevelEval.Count(); ++i)
{
if(this->m_ttdTopLevelEval.Item(i).ContextSpecificBodyPtrId == trgtid)
{
return this->m_ttdTopLevelEval.Item(i).TopLevelBodyCtr;
}
}
TTDAssert(false, "We are missing a top-level function reference.");
return 0;
}
Js::FunctionBody* ScriptContextTTD::FindRootBodyByTopLevelCtr(uint32 bodyCtrId) const
{
for(int32 i = 0; i < this->m_ttdTopLevelScriptLoad.Count(); ++i)
{
if(this->m_ttdTopLevelScriptLoad.Item(i).TopLevelBodyCtr == bodyCtrId)
{
return TTD_COERCE_PTR_ID_TO_FUNCTIONBODY(this->m_ttdTopLevelScriptLoad.Item(i).ContextSpecificBodyPtrId);
}
}
for(int32 i = 0; i < this->m_ttdTopLevelNewFunction.Count(); ++i)
{
if(this->m_ttdTopLevelNewFunction.Item(i).TopLevelBodyCtr == bodyCtrId)
{
return TTD_COERCE_PTR_ID_TO_FUNCTIONBODY(this->m_ttdTopLevelNewFunction.Item(i).ContextSpecificBodyPtrId);
}
}
for(int32 i = 0; i < this->m_ttdTopLevelEval.Count(); ++i)
{
if(this->m_ttdTopLevelEval.Item(i).TopLevelBodyCtr == bodyCtrId)
{
return TTD_COERCE_PTR_ID_TO_FUNCTIONBODY(this->m_ttdTopLevelEval.Item(i).ContextSpecificBodyPtrId);
}
}
//TTDAssert(false, "We are missing a top level body counter.");
return nullptr;
}
void ScriptContextTTD::ClearLoadedSourcesForSnapshotRestore()
{
this->m_ttdTopLevelScriptLoad.Clear();
this->m_ttdTopLevelNewFunction.Clear();
this->m_ttdTopLevelEval.Clear();
this->m_ttdPinnedRootFunctionSet->Clear();
this->m_ttdFunctionBodyParentMap.Clear();
}
//////////////////
void RuntimeContextInfo::BuildPathString(UtilSupport::TTAutoString rootpath, const char16* name, const char16* optaccessortag, UtilSupport::TTAutoString& into)
{
into.Append(rootpath);
into.Append(_u("."));
into.Append(name);
if(optaccessortag != nullptr)
{
into.Append(optaccessortag);
}
}
void RuntimeContextInfo::LoadAndOrderPropertyNames(Js::RecyclableObject* obj, JsUtil::List<const Js::PropertyRecord*, HeapAllocator>& propertyList)
{
TTDAssert(propertyList.Count() == 0, "This should be empty.");
Js::ScriptContext* ctx = obj->GetScriptContext();
uint32 propcount = (uint32)obj->GetPropertyCount();
//get all of the properties
for(uint32 i = 0; i < propcount; ++i)
{
Js::PropertyIndex propertyIndex = (Js::PropertyIndex)i;
Js::PropertyId propertyId = obj->GetPropertyId(propertyIndex);
if((propertyId != Js::Constants::NoProperty) & (!Js::IsInternalPropertyId(propertyId)))
{
TTDAssert(obj->HasOwnProperty(propertyId), "We are assuming this is own property count.");
propertyList.Add(ctx->GetPropertyName(propertyId));
}
}
//now sort the list so the traversal order is stable
//Rock a custom shell sort!!!!
const int32 gaps[6] = { 132, 57, 23, 10, 4, 1 };
int32 llen = propertyList.Count();
for(uint32 gapi = 0; gapi < 6; ++gapi)
{
int32 gap = gaps[gapi];
for(int32 i = gap; i < llen; i++)
{
const Js::PropertyRecord* temp = propertyList.Item(i);
int32 j = 0;
for(j = i; j >= gap && PropertyNameCmp(propertyList.Item(j - gap), temp); j -= gap)
{
const Js::PropertyRecord* shiftElem = propertyList.Item(j - gap);
propertyList.SetItem(j, shiftElem);
}
propertyList.SetItem(j, temp);
}
}
}
bool RuntimeContextInfo::PropertyNameCmp(const Js::PropertyRecord* p1, const Js::PropertyRecord* p2)
{
if(p1->GetLength() != p2->GetLength())
{
return p1->GetLength() > p2->GetLength();
}
else
{
return wcscmp(p1->GetBuffer(), p2->GetBuffer()) > 0;
}
}
RuntimeContextInfo::RuntimeContextInfo()
: m_worklist(&HeapAllocator::Instance), m_nullString(),
m_coreObjToPathMap(&HeapAllocator::Instance, TTD_CORE_OBJECT_COUNT), m_coreBodyToPathMap(&HeapAllocator::Instance, TTD_CORE_FUNCTION_BODY_COUNT), m_coreDbgScopeToPathMap(&HeapAllocator::Instance, TTD_CORE_FUNCTION_BODY_COUNT),
m_sortedObjectList(&HeapAllocator::Instance, TTD_CORE_OBJECT_COUNT), m_sortedFunctionBodyList(&HeapAllocator::Instance, TTD_CORE_FUNCTION_BODY_COUNT), m_sortedDbgScopeList(&HeapAllocator::Instance, TTD_CORE_FUNCTION_BODY_COUNT)
{
;
}
RuntimeContextInfo::~RuntimeContextInfo()
{
for(auto iter = this->m_coreObjToPathMap.GetIterator(); iter.IsValid(); iter.MoveNext())
{
TT_HEAP_DELETE(UtilSupport::TTAutoString, iter.CurrentValue());
}
for(auto iter = this->m_coreBodyToPathMap.GetIterator(); iter.IsValid(); iter.MoveNext())
{
TT_HEAP_DELETE(UtilSupport::TTAutoString, iter.CurrentValue());
}
for(auto iter = this->m_coreDbgScopeToPathMap.GetIterator(); iter.IsValid(); iter.MoveNext())
{
TT_HEAP_DELETE(UtilSupport::TTAutoString, iter.CurrentValue());
}
}
//Mark all the well-known objects/values/types from this script context
void RuntimeContextInfo::MarkWellKnownObjects_TTD(MarkTable& marks) const
{
for(int32 i = 0; i < this->m_sortedObjectList.Count(); ++i)
{
Js::RecyclableObject* obj = this->m_sortedObjectList.Item(i);
marks.MarkAddrWithSpecialInfo<MarkTableTag::JsWellKnownObj>(obj);
}
for(int32 i = 0; i < this->m_sortedFunctionBodyList.Count(); ++i)
{
Js::FunctionBody* body = this->m_sortedFunctionBodyList.Item(i);
marks.MarkAddrWithSpecialInfo<MarkTableTag::JsWellKnownObj>(body);
}
}
TTD_WELLKNOWN_TOKEN RuntimeContextInfo::ResolvePathForKnownObject(Js::RecyclableObject* obj) const
{
const UtilSupport::TTAutoString* res = this->m_coreObjToPathMap.Item(obj);
return res->GetStrValue();
}
TTD_WELLKNOWN_TOKEN RuntimeContextInfo::ResolvePathForKnownFunctionBody(Js::FunctionBody* fbody) const
{
const UtilSupport::TTAutoString* res = this->m_coreBodyToPathMap.Item(fbody);
return res->GetStrValue();
}
TTD_WELLKNOWN_TOKEN RuntimeContextInfo::ResolvePathForKnownDbgScopeIfExists(Js::DebuggerScope* dbgScope) const
{
const UtilSupport::TTAutoString* res = this->m_coreDbgScopeToPathMap.LookupWithKey(dbgScope, nullptr);
if(res == nullptr)
{
return nullptr;
}
return res->GetStrValue();
}
Js::RecyclableObject* RuntimeContextInfo::LookupKnownObjectFromPath(TTD_WELLKNOWN_TOKEN pathIdString) const
{
int32 pos = LookupPositionInDictNameList<Js::RecyclableObject*, true>(pathIdString, this->m_coreObjToPathMap, this->m_sortedObjectList, this->m_nullString);
TTDAssert(pos != -1, "This isn't a well known object!");
return this->m_sortedObjectList.Item(pos);
}
Js::FunctionBody* RuntimeContextInfo::LookupKnownFunctionBodyFromPath(TTD_WELLKNOWN_TOKEN pathIdString) const
{
int32 pos = LookupPositionInDictNameList<Js::FunctionBody*, true>(pathIdString, this->m_coreBodyToPathMap, this->m_sortedFunctionBodyList, this->m_nullString);
TTDAssert(pos != -1, "Missing function.");
return (pos != -1) ? this->m_sortedFunctionBodyList.Item(pos) : nullptr;
}
Js::DebuggerScope* RuntimeContextInfo::LookupKnownDebuggerScopeFromPath(TTD_WELLKNOWN_TOKEN pathIdString) const
{
int32 pos = LookupPositionInDictNameList<Js::DebuggerScope*, true>(pathIdString, this->m_coreDbgScopeToPathMap, this->m_sortedDbgScopeList, this->m_nullString);
TTDAssert(pos != -1, "Missing debug scope.");
return (pos != -1) ? this->m_sortedDbgScopeList.Item(pos) : nullptr;
}
void RuntimeContextInfo::GatherKnownObjectToPathMap(Js::ScriptContext* ctx)
{
JsUtil::List<const Js::PropertyRecord*, HeapAllocator> propertyRecordList(&HeapAllocator::Instance);
this->EnqueueRootPathObject(_u("global"), ctx->GetGlobalObject());
this->EnqueueRootPathObject(_u("null"), ctx->GetLibrary()->GetNull());
this->EnqueueRootPathObject(_u("_defaultAccessor"), ctx->GetLibrary()->GetDefaultAccessorFunction());
if(ctx->GetConfig()->IsErrorStackTraceEnabled())
{
this->EnqueueRootPathObject(_u("_stackTraceAccessor"), ctx->GetLibrary()->GetStackTraceAccessorFunction());
}
this->EnqueueRootPathObject(_u("_throwTypeErrorRestrictedPropertyAccessor"), ctx->GetLibrary()->GetThrowTypeErrorRestrictedPropertyAccessorFunction());
if(ctx->GetConfig()->IsES6PromiseEnabled())
{
this->EnqueueRootPathObject(_u("_identityFunction"), ctx->GetLibrary()->GetIdentityFunction());
this->EnqueueRootPathObject(_u("_throwerFunction"), ctx->GetLibrary()->GetThrowerFunction());
}
this->EnqueueRootPathObject(_u("_arrayIteratorPrototype"), ctx->GetLibrary()->GetArrayIteratorPrototype());
this->EnqueueRootPathObject(_u("_mapIteratorPrototype"), ctx->GetLibrary()->GetMapIteratorPrototype());
this->EnqueueRootPathObject(_u("_setIteratorPrototype"), ctx->GetLibrary()->GetSetIteratorPrototype());
this->EnqueueRootPathObject(_u("_stringIteratorPrototype"), ctx->GetLibrary()->GetStringIteratorPrototype());
uint32 counter = 0;
while(!this->m_worklist.Empty())
{
Js::RecyclableObject* curr = this->m_worklist.Dequeue();
counter++;
////
//Handle the standard properties for all object types
//load propArray with all property names
propertyRecordList.Clear();
LoadAndOrderPropertyNames(curr, propertyRecordList);
//access each property and process the target objects as needed
for(int32 i = 0; i < propertyRecordList.Count(); ++i)
{
const Js::PropertyRecord* precord = propertyRecordList.Item(i);
Js::Var getter = nullptr;
Js::Var setter = nullptr;
if(curr->GetAccessors(precord->GetPropertyId(), &getter, &setter, ctx))
{
if(getter != nullptr && !Js::JavascriptOperators::IsUndefinedObject(getter))
{
TTDAssert(Js::JavascriptFunction::Is(getter), "The getter is not a function?");
this->EnqueueNewPathVarAsNeeded(curr, getter, precord, _u(">"));
}
if(setter != nullptr && !Js::JavascriptOperators::IsUndefinedObject(setter))
{
TTDAssert(Js::JavascriptFunction::Is(setter), "The setter is not a function?");
this->EnqueueNewPathVarAsNeeded(curr, Js::RecyclableObject::FromVar(setter), precord, _u("<"));
}
}
else
{
Js::Var pitem = nullptr;
BOOL isproperty = Js::JavascriptOperators::GetOwnProperty(curr, precord->GetPropertyId(), &pitem, ctx);
TTDAssert(isproperty, "Not sure what went wrong.");
this->EnqueueNewPathVarAsNeeded(curr, pitem, precord, nullptr);
}
}
//shouldn't have any dynamic array valued properties
TTDAssert(!Js::DynamicType::Is(curr->GetTypeId()) || (Js::DynamicObject::FromVar(curr))->GetObjectArray() == nullptr || (Js::DynamicObject::FromVar(curr))->GetObjectArray()->GetLength() == 0, "Shouldn't have any dynamic array valued properties at this point.");
Js::RecyclableObject* proto = curr->GetPrototype();
bool skipProto = (proto == nullptr) || Js::JavascriptOperators::IsUndefinedOrNullType(proto->GetTypeId());
if(!skipProto)
{
this->EnqueueNewPathVarAsNeeded(curr, proto, _u("_proto_"));
}
curr->ProcessCorePaths();
}
SortDictIntoListOnNames<Js::RecyclableObject*>(this->m_coreObjToPathMap, this->m_sortedObjectList, this->m_nullString);
SortDictIntoListOnNames<Js::FunctionBody*>(this->m_coreBodyToPathMap, this->m_sortedFunctionBodyList, this->m_nullString);
SortDictIntoListOnNames<Js::DebuggerScope*>(this->m_coreDbgScopeToPathMap, this->m_sortedDbgScopeList, this->m_nullString);
}
////
void RuntimeContextInfo::EnqueueRootPathObject(const char16* rootName, Js::RecyclableObject* obj)
{
this->m_worklist.Enqueue(obj);
UtilSupport::TTAutoString* rootStr = TT_HEAP_NEW(UtilSupport::TTAutoString, rootName);
TTDAssert(!this->m_coreObjToPathMap.ContainsKey(obj), "Already in map!!!");
this->m_coreObjToPathMap.AddNew(obj, rootStr);
}
void RuntimeContextInfo::EnqueueNewPathVarAsNeeded(Js::RecyclableObject* parent, Js::Var val, const Js::PropertyRecord* prop, const char16* optacessortag)
{
this->EnqueueNewPathVarAsNeeded(parent, val, prop->GetBuffer(), optacessortag);
}
void RuntimeContextInfo::EnqueueNewPathVarAsNeeded(Js::RecyclableObject* parent, Js::Var val, const char16* propName, const char16* optacessortag)
{
if(JsSupport::IsVarTaggedInline(val))
{
return;
}
if(JsSupport::IsVarPrimitiveKind(val) && !Js::GlobalObject::Is(parent))
{
return; //we keep primitives from global object only -- may need others but this is a simple way to start to get undefined, null, infy, etc.
}
Js::RecyclableObject* obj = Js::RecyclableObject::FromVar(val);
if(!this->m_coreObjToPathMap.ContainsKey(obj))
{
const UtilSupport::TTAutoString* ppath = this->m_coreObjToPathMap.Item(parent);
this->m_worklist.Enqueue(obj);
UtilSupport::TTAutoString* tpath = TT_HEAP_NEW(UtilSupport::TTAutoString, *ppath);
tpath->Append(_u("."));
tpath->Append(propName);
if(optacessortag != nullptr)
{
tpath->Append(optacessortag);
}
TTDAssert(!this->m_coreObjToPathMap.ContainsKey(obj), "Already in map!!!");
this->m_coreObjToPathMap.AddNew(obj, tpath);
}
}
void RuntimeContextInfo::EnqueueNewFunctionBodyObject(Js::RecyclableObject* parent, Js::FunctionBody* fbody, const char16* name)
{
if(!this->m_coreBodyToPathMap.ContainsKey(fbody))
{
fbody->EnsureDeserialized();
const UtilSupport::TTAutoString* ppath = this->m_coreObjToPathMap.Item(parent);
UtilSupport::TTAutoString* fpath = TT_HEAP_NEW(UtilSupport::TTAutoString, *ppath);
fpath->Append(_u("."));
fpath->Append(name);
this->m_coreBodyToPathMap.AddNew(fbody, fpath);
}
}
void RuntimeContextInfo::AddWellKnownDebuggerScopePath(Js::RecyclableObject* parent, Js::DebuggerScope* dbgScope, uint32 index)
{
if(!this->m_coreDbgScopeToPathMap.ContainsKey(dbgScope))
{
const UtilSupport::TTAutoString* ppath = this->m_coreObjToPathMap.Item(parent);
UtilSupport::TTAutoString* scpath = TT_HEAP_NEW(UtilSupport::TTAutoString, *ppath);
scpath->Append(_u(".!scope["));
scpath->Append(index);
scpath->Append(_u("]"));
this->m_coreDbgScopeToPathMap.AddNew(dbgScope, scpath);
}
}
void RuntimeContextInfo::BuildArrayIndexBuffer(uint32 arrayidx, UtilSupport::TTAutoString& res)
{
res.Append(_u("!arrayContents["));
res.Append(arrayidx);
res.Append(_u("]"));
}
void RuntimeContextInfo::BuildEnvironmentIndexBuffer(uint32 envidx, UtilSupport::TTAutoString& res)
{
res.Append(_u("!env["));
res.Append(envidx);
res.Append(_u("]"));
}
void RuntimeContextInfo::BuildEnvironmentIndexAndSlotBuffer(uint32 envidx, uint32 slotidx, UtilSupport::TTAutoString& res)
{
res.Append(_u("!env["));
res.Append(envidx);
res.Append(_u("].!slot["));
res.Append(slotidx);
res.Append(_u("]"));
}
}
#endif