| //------------------------------------------------------------------------------------------------------- |
| // 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" |
| |
| // Parser includes |
| #include "CharClassifier.h" |
| // TODO: clean up the need of these regex related header here just for GroupInfo needed in JavascriptRegExpConstructor |
| #include "RegexCommon.h" |
| |
| // Runtime includes |
| #include "Library/ObjectPrototypeObject.h" |
| #include "Library/JavascriptNumberObject.h" |
| #include "Library/BoundFunction.h" |
| #include "Library/JavascriptRegExpConstructor.h" |
| #include "Library/SameValueComparer.h" |
| #include "Library/MapOrSetDataList.h" |
| #include "Library/JavascriptPromise.h" |
| #include "Library/JavascriptProxy.h" |
| #include "Library/JavascriptMap.h" |
| #include "Library/JavascriptSet.h" |
| #include "Library/JavascriptWeakMap.h" |
| #include "Library/JavascriptWeakSet.h" |
| #include "Library/ArgumentsObject.h" |
| |
| #include "Types/DynamicObjectPropertyEnumerator.h" |
| #include "Types/JavascriptStaticEnumerator.h" |
| #include "Library/ForInObjectEnumerator.h" |
| #include "Library/ES5Array.h" |
| #include "Library/SimdLib.h" |
| |
| namespace Js |
| { |
| #define RETURN_VALUE_MAX_NAME 255 |
| #define PENDING_MUTATION_VALUE_MAX_NAME 255 |
| |
| ArenaAllocator *GetArenaFromContext(ScriptContext *scriptContext) |
| { |
| Assert(scriptContext); |
| return scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena()->Arena(); |
| } |
| |
| template <class T> |
| WeakArenaReference<IDiagObjectModelWalkerBase>* CreateAWalker(ScriptContext * scriptContext, Var instance, Var originalInstance) |
| { |
| ReferencedArenaAdapter* pRefArena = scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena(); |
| if (pRefArena) |
| { |
| IDiagObjectModelWalkerBase* pOMWalker = Anew(pRefArena->Arena(), T, scriptContext, instance, originalInstance); |
| return HeapNew(WeakArenaReference<IDiagObjectModelWalkerBase>,pRefArena, pOMWalker); |
| } |
| return nullptr; |
| } |
| //----------------------- |
| // ResolvedObject |
| |
| |
| WeakArenaReference<IDiagObjectModelDisplay>* ResolvedObject::GetObjectDisplay() |
| { |
| AssertMsg(typeId != TypeIds_HostDispatch, "Bad usage of ResolvedObject::GetObjectDisplay"); |
| |
| IDiagObjectModelDisplay* pOMDisplay = (this->objectDisplay != nullptr) ? this->objectDisplay : CreateDisplay(); |
| Assert(pOMDisplay); |
| |
| return HeapNew(WeakArenaReference<IDiagObjectModelDisplay>, scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena(), pOMDisplay); |
| } |
| |
| IDiagObjectModelDisplay * ResolvedObject::CreateDisplay() |
| { |
| IDiagObjectModelDisplay* pOMDisplay = nullptr; |
| ReferencedArenaAdapter* pRefArena = scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena(); |
| |
| if (Js::TypedArrayBase::Is(obj)) |
| { |
| pOMDisplay = Anew(pRefArena->Arena(), RecyclableTypedArrayDisplay, this); |
| } |
| else if (Js::ES5Array::Is(obj)) |
| { |
| pOMDisplay = Anew(pRefArena->Arena(), RecyclableES5ArrayDisplay, this); |
| } |
| else if (Js::JavascriptArray::Is(obj)) |
| { |
| // DisableJIT-TODO: Review- is this correct? |
| #if ENABLE_COPYONACCESS_ARRAY |
| // Make sure any NativeIntArrays are converted |
| Js::JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray<Var>(obj); |
| #endif |
| pOMDisplay = Anew(pRefArena->Arena(), RecyclableArrayDisplay, this); |
| } |
| #ifdef ENABLE_SIMDJS |
| else if (Js::JavascriptSIMDInt32x4::Is(obj)) |
| { |
| pOMDisplay = Anew(pRefArena->Arena(), RecyclableSimdInt32x4ObjectDisplay, this); |
| } |
| else if (Js::JavascriptSIMDFloat32x4::Is(obj)) |
| { |
| pOMDisplay = Anew(pRefArena->Arena(), RecyclableSimdFloat32x4ObjectDisplay, this); |
| } |
| else if (Js::JavascriptSIMDInt8x16::Is(obj)) |
| { |
| pOMDisplay = Anew(pRefArena->Arena(), RecyclableSimdInt8x16ObjectDisplay, this); |
| } |
| else if (Js::JavascriptSIMDInt16x8::Is(obj)) |
| { |
| pOMDisplay = Anew(pRefArena->Arena(), RecyclableSimdInt16x8ObjectDisplay, this); |
| } |
| else if (Js::JavascriptSIMDBool32x4::Is(obj)) |
| { |
| pOMDisplay = Anew(pRefArena->Arena(), RecyclableSimdBool32x4ObjectDisplay, this); |
| } |
| else if (Js::JavascriptSIMDBool8x16::Is(obj)) |
| { |
| pOMDisplay = Anew(pRefArena->Arena(), RecyclableSimdBool8x16ObjectDisplay, this); |
| } |
| else if (Js::JavascriptSIMDBool16x8::Is(obj)) |
| { |
| pOMDisplay = Anew(pRefArena->Arena(), RecyclableSimdBool16x8ObjectDisplay, this); |
| } |
| else if (Js::JavascriptSIMDUint32x4::Is(obj)) |
| { |
| pOMDisplay = Anew(pRefArena->Arena(), RecyclableSimdUint32x4ObjectDisplay, this); |
| } |
| else if (Js::JavascriptSIMDUint8x16::Is(obj)) |
| { |
| pOMDisplay = Anew(pRefArena->Arena(), RecyclableSimdUint8x16ObjectDisplay, this); |
| } |
| else if (Js::JavascriptSIMDUint16x8::Is(obj)) |
| { |
| pOMDisplay = Anew(pRefArena->Arena(), RecyclableSimdUint16x8ObjectDisplay, this); |
| } |
| #endif |
| else |
| { |
| pOMDisplay = Anew(pRefArena->Arena(), RecyclableObjectDisplay, this); |
| } |
| |
| if (this->isConst || this->propId == Js::PropertyIds::_superReferenceSymbol || this->propId == Js::PropertyIds::_superCtorReferenceSymbol) |
| { |
| pOMDisplay->SetDefaultTypeAttribute(DBGPROP_ATTRIB_VALUE_READONLY); |
| } |
| |
| return pOMDisplay; |
| } |
| |
| bool ResolvedObject::IsInDeadZone() const |
| { |
| Assert(scriptContext); |
| return this->obj == scriptContext->GetLibrary()->GetDebuggerDeadZoneBlockVariableString(); |
| } |
| |
| //----------------------- |
| // LocalsDisplay |
| |
| |
| LocalsDisplay::LocalsDisplay(DiagStackFrame* _frame) |
| : pFrame(_frame) |
| { |
| } |
| |
| LPCWSTR LocalsDisplay::Name() |
| { |
| return _u("Locals"); |
| } |
| |
| LPCWSTR LocalsDisplay::Type() |
| { |
| return _u(""); |
| } |
| |
| LPCWSTR LocalsDisplay::Value(int radix) |
| { |
| return _u("Locals"); |
| } |
| |
| BOOL LocalsDisplay::HasChildren() |
| { |
| Js::JavascriptFunction* func = pFrame->GetJavascriptFunction(); |
| |
| FunctionBody* function = func->GetFunctionBody(); |
| return function && function->GetLocalsCount() != 0; |
| } |
| |
| DBGPROP_ATTRIB_FLAGS LocalsDisplay::GetTypeAttribute() |
| { |
| return DBGPROP_ATTRIB_NO_ATTRIB; |
| } |
| |
| BOOL LocalsDisplay::Set(Var updateObject) |
| { |
| // This is the hidden root object for Locals it doesn't get updated. |
| return FALSE; |
| } |
| |
| WeakArenaReference<IDiagObjectModelWalkerBase>* LocalsDisplay::CreateWalker() |
| { |
| ReferencedArenaAdapter* pRefArena = pFrame->GetScriptContext()->GetThreadContext()->GetDebugManager()->GetDiagnosticArena(); |
| if (pRefArena) |
| { |
| IDiagObjectModelWalkerBase * pOMWalker = nullptr; |
| |
| IGNORE_STACKWALK_EXCEPTION(scriptContext); |
| pOMWalker = Anew(pRefArena->Arena(), LocalsWalker, pFrame, FrameWalkerFlags::FW_MakeGroups); |
| |
| return HeapNew(WeakArenaReference<IDiagObjectModelWalkerBase>,pRefArena, pOMWalker); |
| } |
| return nullptr; |
| } |
| |
| // Variables on the scope or in current function. |
| |
| /*static*/ |
| BOOL VariableWalkerBase::GetExceptionObject(int &index, DiagStackFrame* frame, ResolvedObject* pResolvedObject) |
| { |
| Assert(pResolvedObject); |
| Assert(pResolvedObject->scriptContext); |
| Assert(frame); |
| Assert(index >= 0); |
| |
| if (HasExceptionObject(frame)) |
| { |
| if (index == 0) |
| { |
| pResolvedObject->name = _u("{exception}"); |
| pResolvedObject->typeId = TypeIds_Error; |
| pResolvedObject->address = nullptr; |
| pResolvedObject->obj = pResolvedObject->scriptContext->GetDebugContext()->GetProbeContainer()->GetExceptionObject(); |
| |
| if (pResolvedObject->obj == nullptr) |
| { |
| Assert(false); |
| pResolvedObject->obj = pResolvedObject->scriptContext->GetLibrary()->GetUndefined(); |
| } |
| return TRUE; |
| } |
| |
| // Adjust the index |
| index -= 1; |
| } |
| |
| return FALSE; |
| } |
| |
| /*static*/ |
| bool VariableWalkerBase::HasExceptionObject(DiagStackFrame* frame) |
| { |
| Assert(frame); |
| Assert(frame->GetScriptContext()); |
| |
| return frame->GetScriptContext()->GetDebugContext()->GetProbeContainer()->GetExceptionObject() != nullptr; |
| } |
| |
| /*static*/ |
| void VariableWalkerBase::GetReturnedValueResolvedObject(ReturnedValue * returnValue, DiagStackFrame* frame, ResolvedObject* pResolvedObject) |
| { |
| DBGPROP_ATTRIB_FLAGS defaultAttributes = DBGPROP_ATTRIB_VALUE_IS_RETURN_VALUE | DBGPROP_ATTRIB_VALUE_IS_FAKE; |
| WCHAR * finalName = AnewArray(GetArenaFromContext(pResolvedObject->scriptContext), WCHAR, RETURN_VALUE_MAX_NAME); |
| if (returnValue->isValueOfReturnStatement) |
| { |
| swprintf_s(finalName, RETURN_VALUE_MAX_NAME, _u("[Return value]")); |
| pResolvedObject->obj = frame->GetRegValue(Js::FunctionBody::ReturnValueRegSlot); |
| pResolvedObject->address = Anew(frame->GetArena(), LocalObjectAddressForRegSlot, frame, Js::FunctionBody::ReturnValueRegSlot, pResolvedObject->obj); |
| } |
| else |
| { |
| if (returnValue->calledFunction->IsScriptFunction()) |
| { |
| swprintf_s(finalName, RETURN_VALUE_MAX_NAME, _u("[%s returned]"), returnValue->calledFunction->GetFunctionBody()->GetDisplayName()); |
| } |
| else |
| { |
| Js::JavascriptString *builtInName = returnValue->calledFunction->GetDisplayName(); |
| swprintf_s(finalName, RETURN_VALUE_MAX_NAME, _u("[%s returned]"), builtInName->GetSz()); |
| } |
| pResolvedObject->obj = returnValue->returnedValue; |
| defaultAttributes |= DBGPROP_ATTRIB_VALUE_READONLY; |
| pResolvedObject->address = nullptr; |
| } |
| Assert(pResolvedObject->obj != nullptr); |
| |
| pResolvedObject->name = finalName; |
| pResolvedObject->typeId = TypeIds_Object; |
| |
| pResolvedObject->objectDisplay = pResolvedObject->CreateDisplay(); |
| pResolvedObject->objectDisplay->SetDefaultTypeAttribute(defaultAttributes); |
| } |
| |
| /*static*/ |
| BOOL VariableWalkerBase::GetReturnedValue(int &index, DiagStackFrame* frame, ResolvedObject* pResolvedObject) |
| { |
| Assert(pResolvedObject); |
| Assert(pResolvedObject->scriptContext); |
| Assert(frame); |
| Assert(index >= 0); |
| ReturnedValueList *returnedValueList = frame->GetScriptContext()->GetDebugContext()->GetProbeContainer()->GetReturnedValueList(); |
| |
| if (returnedValueList != nullptr && returnedValueList->Count() > 0 && frame->IsTopFrame()) |
| { |
| if (index < returnedValueList->Count()) |
| { |
| ReturnedValue * returnValue = returnedValueList->Item(index); |
| VariableWalkerBase::GetReturnedValueResolvedObject(returnValue, frame, pResolvedObject); |
| return TRUE; |
| } |
| |
| // Adjust the index |
| index -= returnedValueList->Count(); |
| } |
| |
| return FALSE; |
| } |
| |
| /*static*/ |
| int VariableWalkerBase::GetReturnedValueCount(DiagStackFrame* frame) |
| { |
| Assert(frame); |
| Assert(frame->GetScriptContext()); |
| |
| ReturnedValueList *returnedValueList = frame->GetScriptContext()->GetDebugContext()->GetProbeContainer()->GetReturnedValueList(); |
| return returnedValueList != nullptr && frame->IsTopFrame() ? returnedValueList->Count() : 0; |
| } |
| |
| #ifdef ENABLE_MUTATION_BREAKPOINT |
| BOOL VariableWalkerBase::GetBreakMutationBreakpointValue(int &index, DiagStackFrame* frame, ResolvedObject* pResolvedObject) |
| { |
| Assert(pResolvedObject); |
| Assert(pResolvedObject->scriptContext); |
| Assert(frame); |
| Assert(index >= 0); |
| |
| Js::MutationBreakpoint *mutationBreakpoint = frame->GetScriptContext()->GetDebugContext()->GetProbeContainer()->GetDebugManager()->GetActiveMutationBreakpoint(); |
| |
| if (mutationBreakpoint != nullptr) |
| { |
| if (index == 0) |
| { |
| pResolvedObject->name = _u("[Pending Mutation]"); |
| pResolvedObject->typeId = TypeIds_Object; |
| pResolvedObject->address = nullptr; |
| pResolvedObject->obj = mutationBreakpoint->GetMutationObjectVar(); |
| ReferencedArenaAdapter* pRefArena = pResolvedObject->scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena(); |
| pResolvedObject->objectDisplay = Anew(pRefArena->Arena(), PendingMutationBreakpointDisplay, pResolvedObject, mutationBreakpoint->GetBreakMutationType()); |
| pResolvedObject->objectDisplay->SetDefaultTypeAttribute(DBGPROP_ATTRIB_VALUE_PENDING_MUTATION | DBGPROP_ATTRIB_VALUE_READONLY | DBGPROP_ATTRIB_VALUE_IS_FAKE); |
| return TRUE; |
| } |
| index -= 1; // Adjust the index |
| } |
| |
| return FALSE; |
| } |
| |
| uint VariableWalkerBase::GetBreakMutationBreakpointsCount(DiagStackFrame* frame) |
| { |
| Assert(frame); |
| Assert(frame->GetScriptContext()); |
| |
| return frame->GetScriptContext()->GetDebugContext()->GetProbeContainer()->GetDebugManager()->GetActiveMutationBreakpoint() != nullptr ? 1 : 0; |
| } |
| #endif |
| BOOL VariableWalkerBase::Get(int i, ResolvedObject* pResolvedObject) |
| { |
| AssertMsg(pResolvedObject, "Bad usage of VariableWalkerBase::Get"); |
| |
| Assert(pFrame); |
| pResolvedObject->scriptContext = pFrame->GetScriptContext(); |
| |
| if (i < 0) |
| { |
| return FALSE; |
| } |
| |
| if (GetMemberCount() > i) |
| { |
| pResolvedObject->propId = pMembersList->Item(i)->propId; |
| Assert(pResolvedObject->propId != Js::Constants::NoProperty); |
| Assert(!Js::IsInternalPropertyId(pResolvedObject->propId)); |
| |
| if (pResolvedObject->propId == Js::PropertyIds::_superReferenceSymbol || pResolvedObject->propId == Js::PropertyIds::_superCtorReferenceSymbol) |
| { |
| pResolvedObject->name = _u("super"); |
| } |
| else |
| { |
| const Js::PropertyRecord* propertyRecord = pResolvedObject->scriptContext->GetPropertyName(pResolvedObject->propId); |
| pResolvedObject->name = propertyRecord->GetBuffer(); |
| } |
| |
| |
| pResolvedObject->obj = GetVarObjectAt(i); |
| Assert(pResolvedObject->obj); |
| |
| pResolvedObject->typeId = JavascriptOperators::GetTypeId(pResolvedObject->obj); |
| |
| pResolvedObject->address = GetObjectAddress(i); |
| pResolvedObject->isConst = IsConstAt(i); |
| |
| pResolvedObject->objectDisplay = nullptr; |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| Var VariableWalkerBase::GetVarObjectAt(int index) |
| { |
| Assert(index < pMembersList->Count()); |
| return pMembersList->Item(index)->aVar; |
| } |
| |
| bool VariableWalkerBase::IsConstAt(int index) |
| { |
| Assert(index < pMembersList->Count()); |
| DebuggerPropertyDisplayInfo* displayInfo = pMembersList->Item(index); |
| |
| // Dead zone variables are also displayed as read only. |
| return displayInfo->IsConst() || displayInfo->IsInDeadZone(); |
| } |
| |
| uint32 VariableWalkerBase::GetChildrenCount() |
| { |
| PopulateMembers(); |
| return GetMemberCount(); |
| } |
| |
| BOOL VariableWalkerBase::GetGroupObject(ResolvedObject* pResolvedObject) |
| { |
| if (!IsInGroup()) return FALSE; |
| |
| Assert(pResolvedObject); |
| |
| // This is fake [Methods] object. |
| pResolvedObject->name = groupType == UIGroupType_Scope ? _u("[Scope]") : _u("[Globals]"); |
| pResolvedObject->obj = Js::RecyclableObject::FromVar(instance); |
| pResolvedObject->typeId = TypeIds_Function; |
| pResolvedObject->address = nullptr; // Scope object should not be editable |
| |
| ArenaAllocator *arena = GetArenaFromContext(pResolvedObject->scriptContext); |
| Assert(arena); |
| |
| if (groupType == UIGroupType_Scope) |
| { |
| pResolvedObject->objectDisplay = Anew(arena, ScopeVariablesGroupDisplay, this, pResolvedObject); |
| } |
| else |
| { |
| pResolvedObject->objectDisplay = Anew(arena, GlobalsScopeVariablesGroupDisplay, this, pResolvedObject); |
| } |
| |
| return TRUE; |
| } |
| |
| IDiagObjectAddress *VariableWalkerBase::FindPropertyAddress(PropertyId propId, bool& isConst) |
| { |
| PopulateMembers(); |
| if (pMembersList) |
| { |
| for (int i = 0; i < pMembersList->Count(); i++) |
| { |
| DebuggerPropertyDisplayInfo *pair = pMembersList->Item(i); |
| Assert(pair); |
| if (pair->propId == propId) |
| { |
| isConst = pair->IsConst(); |
| return GetObjectAddress(i); |
| } |
| } |
| } |
| return nullptr; |
| } |
| |
| // Determines if the given property is valid for display in the locals window. |
| // Cases in which the property is valid are: |
| // 1. It is not represented by an internal property. |
| // 2. It is a var property. |
| // 3. It is a let/const property in scope and is not in a dead zone (assuming isInDeadZone is nullptr). |
| // (Determines if the given property is currently in block scope and not in a dead zone.) |
| bool VariableWalkerBase::IsPropertyValid(PropertyId propertyId, RegSlot location, bool *isPropertyInDebuggerScope, bool* isConst, bool* isInDeadZone) const |
| { |
| Assert(isPropertyInDebuggerScope); |
| Assert(isConst); |
| *isPropertyInDebuggerScope = false; |
| |
| // Default to writable (for the case of vars and internal properties). |
| *isConst = false; |
| |
| |
| if (!allowLexicalThis && (propertyId == Js::PropertyIds::_lexicalThisSlotSymbol || propertyId == Js::PropertyIds::_lexicalNewTargetSymbol)) |
| { |
| return false; |
| } |
| |
| if (!allowSuperReference && (propertyId == Js::PropertyIds::_superReferenceSymbol || propertyId == Js::PropertyIds::_superCtorReferenceSymbol)) |
| { |
| return false; |
| } |
| |
| if (Js::IsInternalPropertyId(propertyId)) |
| { |
| return false; |
| } |
| |
| Assert(pFrame); |
| Js::FunctionBody *pFBody = pFrame->GetJavascriptFunction()->GetFunctionBody(); |
| |
| if (pFBody && pFBody->GetScopeObjectChain()) |
| { |
| int offset = GetAdjustedByteCodeOffset(); |
| |
| if (pFBody->GetScopeObjectChain()->TryGetDebuggerScopePropertyInfo( |
| propertyId, |
| location, |
| offset, |
| isPropertyInDebuggerScope, |
| isConst, |
| isInDeadZone)) |
| { |
| return true; |
| } |
| } |
| |
| // If the register was not found in any scopes, then it's a var and should be in scope. |
| return !*isPropertyInDebuggerScope; |
| } |
| |
| int VariableWalkerBase::GetAdjustedByteCodeOffset() const |
| { |
| return LocalsWalker::GetAdjustedByteCodeOffset(pFrame); |
| } |
| |
| DebuggerScope * VariableWalkerBase::GetScopeWhenHaltAtFormals() |
| { |
| if (IsWalkerForCurrentFrame()) |
| { |
| return LocalsWalker::GetScopeWhenHaltAtFormals(pFrame); |
| } |
| |
| return nullptr; |
| } |
| |
| bool VariableWalkerBase::IsInParamScope(DebuggerScope* scope, DiagStackFrame* pFrame) |
| { |
| return scope != nullptr && scope->GetEnd() > LocalsWalker::GetAdjustedByteCodeOffset(pFrame); |
| } |
| |
| // Allocates and returns a property display info. |
| DebuggerPropertyDisplayInfo* VariableWalkerBase::AllocateNewPropertyDisplayInfo(PropertyId propertyId, Var value, bool isConst, bool isInDeadZone) |
| { |
| Assert(pFrame); |
| Assert(value); |
| Assert(isInDeadZone || !pFrame->GetScriptContext()->IsUndeclBlockVar(value)); |
| |
| DWORD flags = DebuggerPropertyDisplayInfoFlags_None; |
| flags |= isConst ? DebuggerPropertyDisplayInfoFlags_Const : 0; |
| flags |= isInDeadZone ? DebuggerPropertyDisplayInfoFlags_InDeadZone : 0; |
| |
| ArenaAllocator *arena = pFrame->GetArena(); |
| |
| if (isInDeadZone) |
| { |
| value = pFrame->GetScriptContext()->GetLibrary()->GetDebuggerDeadZoneBlockVariableString(); |
| } |
| |
| return Anew(arena, DebuggerPropertyDisplayInfo, propertyId, value, flags); |
| } |
| |
| /// Slot array |
| |
| void SlotArrayVariablesWalker::PopulateMembers() |
| { |
| if (pMembersList == nullptr && instance != nullptr) |
| { |
| ArenaAllocator *arena = pFrame->GetArena(); |
| ScopeSlots slotArray = GetSlotArray(); |
| |
| if (slotArray.IsFunctionScopeSlotArray()) |
| { |
| DebuggerScope *formalScope = GetScopeWhenHaltAtFormals(); |
| bool isInParamScope = IsInParamScope(formalScope, pFrame); |
| Js::FunctionBody *pFBody = slotArray.GetFunctionInfo()->GetFunctionBody(); |
| |
| if (this->groupType & UIGroupType_Param) |
| { |
| Assert(formalScope != nullptr && pFBody->paramScopeSlotArraySize > 0); |
| uint slotArrayCount = pFBody->paramScopeSlotArraySize; |
| pMembersList = JsUtil::List<DebuggerPropertyDisplayInfo *, ArenaAllocator>::New(arena, slotArrayCount); |
| |
| for (uint32 i = 0; i < slotArrayCount; i++) |
| { |
| Js::DebuggerScopeProperty scopeProperty = formalScope->scopeProperties->Item(i); |
| |
| Var value = slotArray.Get(i); |
| bool isInDeadZone = pFrame->GetScriptContext()->IsUndeclBlockVar(value); |
| |
| DebuggerPropertyDisplayInfo *pair = AllocateNewPropertyDisplayInfo( |
| scopeProperty.propId, |
| value, |
| false/*isConst*/, |
| isInDeadZone); |
| |
| Assert(pair != nullptr); |
| pMembersList->Add(pair); |
| } |
| } |
| else if (pFBody->GetPropertyIdsForScopeSlotArray() != nullptr) |
| { |
| uint slotArrayCount = slotArray.GetCount(); |
| pMembersList = JsUtil::List<DebuggerPropertyDisplayInfo *, ArenaAllocator>::New(arena, slotArrayCount); |
| |
| for (uint32 i = 0; i < slotArrayCount; i++) |
| { |
| Js::PropertyId propertyId = pFBody->GetPropertyIdsForScopeSlotArray()[i]; |
| bool isConst = false; |
| bool isPropertyInDebuggerScope = false; |
| bool isInDeadZone = false; |
| if (propertyId != Js::Constants::NoProperty && IsPropertyValid(propertyId, i, &isPropertyInDebuggerScope, &isConst, &isInDeadZone)) |
| { |
| if (!isInParamScope || formalScope->HasProperty(propertyId)) |
| { |
| Var value = slotArray.Get(i); |
| |
| if (pFrame->GetScriptContext()->IsUndeclBlockVar(value)) |
| { |
| isInDeadZone = true; |
| } |
| |
| DebuggerPropertyDisplayInfo *pair = AllocateNewPropertyDisplayInfo( |
| propertyId, |
| value, |
| isConst, |
| isInDeadZone); |
| |
| Assert(pair != nullptr); |
| pMembersList->Add(pair); |
| } |
| } |
| } |
| } |
| } |
| else |
| { |
| DebuggerScope* debuggerScope = slotArray.GetDebuggerScope(); |
| |
| AssertMsg(debuggerScope, "Slot array debugger scope is missing but should be created."); |
| pMembersList = JsUtil::List<DebuggerPropertyDisplayInfo *, ArenaAllocator>::New(arena); |
| if (debuggerScope->HasProperties()) |
| { |
| debuggerScope->scopeProperties->Map([&] (int i, Js::DebuggerScopeProperty& scopeProperty) |
| { |
| Var value = slotArray.Get(scopeProperty.location); |
| bool isConst = scopeProperty.IsConst(); |
| bool isInDeadZone = false; |
| |
| if (pFrame->GetScriptContext()->IsUndeclBlockVar(value)) |
| { |
| isInDeadZone = true; |
| } |
| |
| DebuggerPropertyDisplayInfo *pair = AllocateNewPropertyDisplayInfo( |
| scopeProperty.propId, |
| value, |
| isConst, |
| isInDeadZone); |
| |
| Assert(pair != nullptr); |
| pMembersList->Add(pair); |
| }); |
| } |
| } |
| } |
| } |
| |
| IDiagObjectAddress * SlotArrayVariablesWalker::GetObjectAddress(int index) |
| { |
| Assert(index < pMembersList->Count()); |
| ScopeSlots slotArray = GetSlotArray(); |
| return Anew(pFrame->GetArena(), LocalObjectAddressForSlot, slotArray, index, pMembersList->Item(index)->aVar); |
| } |
| |
| // Regslot |
| |
| void RegSlotVariablesWalker::PopulateMembers() |
| { |
| if (pMembersList == nullptr) |
| { |
| Js::FunctionBody *pFBody = pFrame->GetJavascriptFunction()->GetFunctionBody(); |
| ArenaAllocator *arena = pFrame->GetArena(); |
| |
| PropertyIdOnRegSlotsContainer *propIdContainer = pFBody->GetPropertyIdOnRegSlotsContainer(); |
| |
| DebuggerScope *formalScope = GetScopeWhenHaltAtFormals(); |
| |
| // this container can be nullptr if there is no locals in current function. |
| if (propIdContainer != nullptr) |
| { |
| RegSlot limit = propIdContainer->formalsUpperBound == Js::Constants::NoRegister ? Js::Constants::NoRegister : pFBody->MapRegSlot(propIdContainer->formalsUpperBound); |
| |
| pMembersList = JsUtil::List<DebuggerPropertyDisplayInfo *, ArenaAllocator>::New(arena); |
| for (uint i = 0; i < propIdContainer->length; i++) |
| { |
| Js::PropertyId propertyId; |
| RegSlot reg; |
| propIdContainer->FetchItemAt(i, pFBody, &propertyId, ®); |
| bool shouldInsert = false; |
| bool isConst = false; |
| bool isInDeadZone = false; |
| |
| if (this->debuggerScope) |
| { |
| DebuggerScopeProperty debuggerScopeProperty; |
| if (this->debuggerScope->TryGetValidProperty(propertyId, reg, GetAdjustedByteCodeOffset(), &debuggerScopeProperty, &isInDeadZone)) |
| { |
| isConst = debuggerScopeProperty.IsConst(); |
| shouldInsert = true; |
| } |
| } |
| else |
| { |
| bool isPropertyInDebuggerScope = false; |
| shouldInsert = IsPropertyValid(propertyId, reg, &isPropertyInDebuggerScope, &isConst, &isInDeadZone) && !isPropertyInDebuggerScope; |
| } |
| |
| if (shouldInsert) |
| { |
| if (IsInParamScope(formalScope, pFrame)) |
| { |
| if (limit != Js::Constants::NoRegister) |
| { |
| shouldInsert = reg <= limit; |
| } |
| else |
| { |
| shouldInsert = formalScope->HasProperty(propertyId); |
| } |
| } |
| else if (!pFBody->IsParamAndBodyScopeMerged() && formalScope->HasProperty(propertyId)) |
| { |
| DebuggerScopeProperty prop; |
| prop.flags = DebuggerScopePropertyFlags_None; |
| prop.propId = 0; |
| formalScope->TryGetProperty(propertyId, reg, &prop); |
| if (prop.flags & DebuggerScopePropertyFlags_HasDuplicateInBody) |
| { |
| shouldInsert = false; |
| } |
| } |
| } |
| |
| if (shouldInsert) |
| { |
| Var value = pFrame->GetRegValue(reg); |
| |
| // If the user didn't supply an arguments object, a fake one will |
| // be created when evaluating LocalsWalker::ShouldInsertFakeArguments(). |
| if (!(propertyId == PropertyIds::arguments && value == nullptr)) |
| { |
| if (pFrame->GetScriptContext()->IsUndeclBlockVar(value)) |
| { |
| isInDeadZone = true; |
| } |
| |
| DebuggerPropertyDisplayInfo *info = AllocateNewPropertyDisplayInfo( |
| propertyId, |
| (Var)reg, |
| isConst, |
| isInDeadZone); |
| |
| Assert(info != nullptr); |
| pMembersList->Add(info); |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| Var RegSlotVariablesWalker::GetVarObjectAndRegAt(int index, RegSlot* reg /*= nullptr*/) |
| { |
| Assert(index < pMembersList->Count()); |
| |
| Var returnedVar = nullptr; |
| RegSlot returnedReg = Js::Constants::NoRegister; |
| |
| DebuggerPropertyDisplayInfo* displayInfo = pMembersList->Item(index); |
| if (displayInfo->IsInDeadZone()) |
| { |
| // The uninitialized string is already set in the var for the dead zone display. |
| Assert(JavascriptString::Is(displayInfo->aVar)); |
| returnedVar = displayInfo->aVar; |
| } |
| else |
| { |
| returnedReg = ::Math::PointerCastToIntegral<RegSlot>(displayInfo->aVar); |
| returnedVar = pFrame->GetRegValue(returnedReg); |
| } |
| |
| if (reg != nullptr) |
| { |
| *reg = returnedReg; |
| } |
| |
| AssertMsg(returnedVar, "Var should be replaced with the dead zone string object."); |
| return returnedVar; |
| } |
| |
| Var RegSlotVariablesWalker::GetVarObjectAt(int index) |
| { |
| return GetVarObjectAndRegAt(index); |
| } |
| |
| IDiagObjectAddress * RegSlotVariablesWalker::GetObjectAddress(int index) |
| { |
| RegSlot reg = Js::Constants::NoRegister; |
| Var obj = GetVarObjectAndRegAt(index, ®); |
| |
| return Anew(pFrame->GetArena(), LocalObjectAddressForRegSlot, pFrame, reg, obj); |
| } |
| |
| // For an activation object. |
| |
| void ObjectVariablesWalker::PopulateMembers() |
| { |
| if (pMembersList == nullptr && instance != nullptr) |
| { |
| ScriptContext * scriptContext = pFrame->GetScriptContext(); |
| ArenaAllocator *arena = GetArenaFromContext(scriptContext); |
| |
| Assert(Js::RecyclableObject::Is(instance)); |
| |
| Js::RecyclableObject* object = Js::RecyclableObject::FromVar(instance); |
| Assert(JavascriptOperators::IsObject(object)); |
| |
| int count = object->GetPropertyCount(); |
| pMembersList = JsUtil::List<DebuggerPropertyDisplayInfo *, ArenaAllocator>::New(arena, count); |
| |
| AddObjectProperties(count, object); |
| } |
| } |
| |
| void ObjectVariablesWalker::AddObjectProperties(int count, Js::RecyclableObject* object) |
| { |
| ScriptContext * scriptContext = pFrame->GetScriptContext(); |
| |
| DebuggerScope *formalScope = LocalsWalker::GetScopeWhenHaltAtFormals(pFrame); |
| |
| // For the scopes and locals only enumerable properties will be shown. |
| for (int i = 0; i < count; i++) |
| { |
| Js::PropertyId propertyId = object->GetPropertyId((PropertyIndex)i); |
| |
| bool isConst = false; |
| bool isPropertyInDebuggerScope = false; |
| bool isInDeadZone = false; |
| if (propertyId != Js::Constants::NoProperty |
| && IsPropertyValid(propertyId, Js::Constants::NoRegister, &isPropertyInDebuggerScope, &isConst, &isInDeadZone) |
| && object->IsEnumerable(propertyId)) |
| { |
| Var itemObj = RecyclableObjectWalker::GetObject(object, object, propertyId, scriptContext); |
| if (itemObj == nullptr) |
| { |
| itemObj = scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| if (IsInParamScope(formalScope, pFrame) && pFrame->GetScriptContext()->IsUndeclBlockVar(itemObj)) |
| { |
| itemObj = scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| AssertMsg(!RootObjectBase::Is(object) || !isConst, "root object shouldn't produce const properties through IsPropertyValid"); |
| |
| DebuggerPropertyDisplayInfo *info = AllocateNewPropertyDisplayInfo( |
| propertyId, |
| itemObj, |
| isConst, |
| isInDeadZone); |
| |
| Assert(info); |
| pMembersList->Add(info); |
| } |
| } |
| } |
| |
| IDiagObjectAddress * ObjectVariablesWalker::GetObjectAddress(int index) |
| { |
| Assert(index < pMembersList->Count()); |
| |
| DebuggerPropertyDisplayInfo* info = pMembersList->Item(index); |
| return Anew(pFrame->GetArena(), RecyclableObjectAddress, instance, info->propId, info->aVar, info->IsInDeadZone() ? TRUE : FALSE); |
| } |
| |
| // For root access on the Global object (adds let/const variables before properties) |
| |
| void RootObjectVariablesWalker::PopulateMembers() |
| { |
| if (pMembersList == nullptr && instance != nullptr) |
| { |
| ScriptContext * scriptContext = pFrame->GetScriptContext(); |
| ArenaAllocator *arena = GetArenaFromContext(scriptContext); |
| |
| Assert(Js::RootObjectBase::Is(instance)); |
| Js::RootObjectBase* object = Js::RootObjectBase::FromVar(instance); |
| |
| int count = object->GetPropertyCount(); |
| pMembersList = JsUtil::List<DebuggerPropertyDisplayInfo *, ArenaAllocator>::New(arena, count); |
| |
| // Add let/const globals first so that they take precedence over the global properties. Then |
| // VariableWalkerBase::FindPropertyAddress will correctly find let/const globals that shadow |
| // global properties of the same name. |
| object->MapLetConstGlobals([&](const PropertyRecord* propertyRecord, Var value, bool isConst) { |
| if (!scriptContext->IsUndeclBlockVar(value)) |
| { |
| // Let/const are always enumerable and valid |
| DebuggerPropertyDisplayInfo *info = AllocateNewPropertyDisplayInfo(propertyRecord->GetPropertyId(), value, isConst, false /*isInDeadZone*/); |
| pMembersList->Add(info); |
| } |
| }); |
| |
| AddObjectProperties(count, object); |
| } |
| } |
| |
| // DiagScopeVariablesWalker |
| |
| DiagScopeVariablesWalker::DiagScopeVariablesWalker(DiagStackFrame* _pFrame, Var _instance, IDiagObjectModelWalkerBase* innerWalker) |
| : VariableWalkerBase(_pFrame, _instance, UIGroupType_InnerScope, /* allowLexicalThis */ false) |
| { |
| ScriptContext * scriptContext = _pFrame->GetScriptContext(); |
| ArenaAllocator *arena = GetArenaFromContext(scriptContext); |
| pDiagScopeObjects = JsUtil::List<IDiagObjectModelWalkerBase *, ArenaAllocator>::New(arena); |
| pDiagScopeObjects->Add(innerWalker); |
| diagScopeVarCount = innerWalker->GetChildrenCount(); |
| scopeIsInitialized = true; |
| } |
| |
| uint32 DiagScopeVariablesWalker::GetChildrenCount() |
| { |
| if (scopeIsInitialized) |
| { |
| return diagScopeVarCount; |
| } |
| Assert(pFrame); |
| Js::FunctionBody *pFBody = pFrame->GetJavascriptFunction()->GetFunctionBody(); |
| |
| if (pFBody->GetScopeObjectChain()) |
| { |
| int bytecodeOffset = GetAdjustedByteCodeOffset(); |
| ScriptContext * scriptContext = pFrame->GetScriptContext(); |
| ArenaAllocator *arena = GetArenaFromContext(scriptContext); |
| pDiagScopeObjects = JsUtil::List<IDiagObjectModelWalkerBase *, ArenaAllocator>::New(arena); |
| |
| // Look for catch/with/block scopes which encompass current offset (skip block scopes as |
| // they are only used for lookup within the RegSlotVariablesWalker). |
| |
| // Go the reverse way so that we find the innermost scope first; |
| Js::ScopeObjectChain * pScopeObjectChain = pFBody->GetScopeObjectChain(); |
| for (int i = pScopeObjectChain->pScopeChain->Count() - 1 ; i >= 0; i--) |
| { |
| Js::DebuggerScope *debuggerScope = pScopeObjectChain->pScopeChain->Item(i); |
| bool isScopeInRange = debuggerScope->IsOffsetInScope(bytecodeOffset); |
| if (isScopeInRange |
| && !debuggerScope->IsParamScope() |
| && (debuggerScope->IsOwnScope() || (debuggerScope->scopeType == DiagBlockScopeDirect && debuggerScope->HasProperties()))) |
| { |
| switch (debuggerScope->scopeType) |
| { |
| case DiagWithScope: |
| { |
| if (enumWithScopeAlso) |
| { |
| RecyclableObjectWalker* recylableObjectWalker = Anew(arena, RecyclableObjectWalker, scriptContext, |
| (Var)pFrame->GetRegValue(debuggerScope->GetLocation(), true)); |
| pDiagScopeObjects->Add(recylableObjectWalker); |
| diagScopeVarCount += recylableObjectWalker->GetChildrenCount(); |
| } |
| } |
| break; |
| case DiagCatchScopeDirect: |
| case DiagCatchScopeInObject: |
| { |
| CatchScopeWalker* catchScopeWalker = Anew(arena, CatchScopeWalker, pFrame, debuggerScope); |
| pDiagScopeObjects->Add(catchScopeWalker); |
| diagScopeVarCount += catchScopeWalker->GetChildrenCount(); |
| } |
| break; |
| case DiagCatchScopeInSlot: |
| case DiagBlockScopeInSlot: |
| { |
| SlotArrayVariablesWalker* blockScopeWalker = Anew(arena, SlotArrayVariablesWalker, pFrame, |
| (Var)pFrame->GetInnerScopeFromRegSlot(debuggerScope->GetLocation()), UIGroupType_InnerScope, /* allowLexicalThis */ false); |
| pDiagScopeObjects->Add(blockScopeWalker); |
| diagScopeVarCount += blockScopeWalker->GetChildrenCount(); |
| } |
| break; |
| case DiagBlockScopeDirect: |
| { |
| RegSlotVariablesWalker *pObjWalker = Anew(arena, RegSlotVariablesWalker, pFrame, debuggerScope, UIGroupType_InnerScope); |
| pDiagScopeObjects->Add(pObjWalker); |
| diagScopeVarCount += pObjWalker->GetChildrenCount(); |
| } |
| break; |
| case DiagBlockScopeInObject: |
| { |
| ObjectVariablesWalker* objectVariablesWalker = Anew(arena, ObjectVariablesWalker, pFrame, pFrame->GetInnerScopeFromRegSlot(debuggerScope->GetLocation()), UIGroupType_InnerScope, /* allowLexicalThis */ false); |
| pDiagScopeObjects->Add(objectVariablesWalker); |
| diagScopeVarCount += objectVariablesWalker->GetChildrenCount(); |
| } |
| break; |
| default: |
| Assert(false); |
| } |
| } |
| } |
| } |
| scopeIsInitialized = true; |
| return diagScopeVarCount; |
| } |
| |
| BOOL DiagScopeVariablesWalker::Get(int i, ResolvedObject* pResolvedObject) |
| { |
| if (i >= 0 && i < (int)diagScopeVarCount) |
| { |
| for (int j = 0; j < pDiagScopeObjects->Count(); j++) |
| { |
| IDiagObjectModelWalkerBase *pObjWalker = pDiagScopeObjects->Item(j); |
| if (i < (int)pObjWalker->GetChildrenCount()) |
| { |
| return pObjWalker->Get(i, pResolvedObject); |
| } |
| i -= (int)pObjWalker->GetChildrenCount(); |
| Assert(i >=0); |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| IDiagObjectAddress * DiagScopeVariablesWalker::FindPropertyAddress(PropertyId propId, bool& isConst) |
| { |
| IDiagObjectAddress * address = nullptr; |
| |
| // Ensure that children are fetched. |
| GetChildrenCount(); |
| |
| if (pDiagScopeObjects) |
| { |
| for (int j = 0; j < pDiagScopeObjects->Count(); j++) |
| { |
| IDiagObjectModelWalkerBase *pObjWalker = pDiagScopeObjects->Item(j); |
| Assert(pObjWalker); |
| |
| address = pObjWalker->FindPropertyAddress(propId, isConst); |
| if (address != nullptr) |
| { |
| break; |
| } |
| } |
| } |
| |
| return address; |
| } |
| |
| // Locals walker |
| |
| LocalsWalker::LocalsWalker(DiagStackFrame* _frame, DWORD _frameWalkerFlags) |
| : pFrame(_frame), frameWalkerFlags(_frameWalkerFlags), pVarWalkers(nullptr), totalLocalsCount(0), hasUserNotDefinedArguments(false) |
| { |
| Js::FunctionBody *pFBody = pFrame->GetJavascriptFunction()->GetFunctionBody(); |
| if (pFBody && !pFBody->GetUtf8SourceInfo()->GetIsLibraryCode()) |
| { |
| // Allocate the container of all walkers. |
| ArenaAllocator *arena = pFrame->GetArena(); |
| pVarWalkers = JsUtil::List<VariableWalkerBase *, ArenaAllocator>::New(arena); |
| |
| // Top most function will have one of these regslot, slotarray or activation object. |
| |
| FrameDisplay * pDisplay = pFrame->GetFrameDisplay(); |
| uint scopeCount = (uint)(pDisplay ? pDisplay->GetLength() : 0); |
| |
| uint nextStartIndex = 0; |
| |
| // Add the catch/with/block expression scope objects. |
| if (pFBody->GetScopeObjectChain()) |
| { |
| pVarWalkers->Add(Anew(arena, DiagScopeVariablesWalker, pFrame, nullptr, !!(frameWalkerFlags & FrameWalkerFlags::FW_EnumWithScopeAlso))); |
| } |
| |
| // In the eval function, we will not show global items directly, instead they should go as a group node. |
| bool shouldAddGlobalItemsDirectly = pFBody->GetIsGlobalFunc() && !pFBody->IsEval(); |
| bool dontAddGlobalsDirectly = (frameWalkerFlags & FrameWalkerFlags::FW_DontAddGlobalsDirectly) == FrameWalkerFlags::FW_DontAddGlobalsDirectly; |
| if (shouldAddGlobalItemsDirectly && !dontAddGlobalsDirectly) |
| { |
| // Global properties will be enumerated using RootObjectVariablesWalker |
| pVarWalkers->Add(Anew(arena, RootObjectVariablesWalker, pFrame, pFrame->GetRootObject(), UIGroupType_None)); |
| } |
| |
| DebuggerScope *formalScope = GetScopeWhenHaltAtFormals(pFrame); |
| DWORD localsType = GetCurrentFramesLocalsType(pFrame); |
| |
| // If we are in the formal scope of a split scoped function then we can skip checking the body scope |
| if (!VariableWalkerBase::IsInParamScope(formalScope, pFrame) || pFBody->IsParamAndBodyScopeMerged()) |
| { |
| VariableWalkerBase *pVarWalker = nullptr; |
| |
| // More than one localsType can occur in the scope |
| if (localsType & FramesLocalType::LocalType_InObject) |
| { |
| Assert(scopeCount > 0); |
| pVarWalker = Anew(arena, ObjectVariablesWalker, pFrame, pDisplay->GetItem(nextStartIndex), UIGroupType_None, !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowLexicalThis), !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowSuperReference)); |
| nextStartIndex++; |
| } |
| else if (localsType & FramesLocalType::LocalType_InSlot) |
| { |
| Assert(scopeCount > 0); |
| pVarWalker = Anew(arena, SlotArrayVariablesWalker, pFrame, (Js::Var *)pDisplay->GetItem(nextStartIndex), UIGroupType_None, !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowLexicalThis), !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowSuperReference)); |
| nextStartIndex++; |
| } |
| else if (scopeCount > 0 && pFBody->GetFrameDisplayRegister() != 0 && pFBody->IsParamAndBodyScopeMerged()) |
| { |
| Assert((Var)pDisplay->GetItem(0) == pFrame->GetScriptContext()->GetLibrary()->GetNull()); |
| nextStartIndex++; |
| } |
| |
| if (pVarWalker) |
| { |
| pVarWalkers->Add(pVarWalker); |
| } |
| } |
| |
| // If we are halted at formal place, and param and body scopes are splitted we need to make use of formal debugger scope to to determine the locals type. |
| if (formalScope != nullptr && !pFBody->IsParamAndBodyScopeMerged()) |
| { |
| if (pFBody->GetPropertyIdOnRegSlotsContainer() && pFBody->GetPropertyIdOnRegSlotsContainer()->formalsUpperBound != Js::Constants::NoRegister) |
| { |
| localsType |= FramesLocalType::LocalType_Reg; |
| } |
| |
| Assert(scopeCount > 0); |
| if (formalScope->scopeType == Js::DiagParamScopeInObject) |
| { |
| // Need to add the param scope frame display as a separate walker as the ObjectVariablesWalker directly uses the socpe object to retrieve properties |
| pVarWalkers->Add(Anew(arena, ObjectVariablesWalker, pFrame, pDisplay->GetItem(nextStartIndex), UIGroupType_Param, !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowLexicalThis), !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowSuperReference))); |
| } |
| else |
| { |
| pVarWalkers->Add(Anew(arena, SlotArrayVariablesWalker, pFrame, (Js::Var *)pDisplay->GetItem(nextStartIndex), UIGroupType_Param, !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowLexicalThis), !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowSuperReference))); |
| } |
| nextStartIndex++; |
| } |
| |
| if (localsType & FramesLocalType::LocalType_Reg) |
| { |
| pVarWalkers->Add(Anew(arena, RegSlotVariablesWalker, pFrame, nullptr /*not debugger scope*/, UIGroupType_None, !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowSuperReference))); |
| } |
| |
| const Js::Var nullVar = pFrame->GetScriptContext()->GetLibrary()->GetNull(); |
| for (uint i = nextStartIndex; i < (uint)scopeCount; i++) |
| { |
| Var currentScopeObject = pDisplay->GetItem(i); |
| if (currentScopeObject != nullptr && currentScopeObject != nullVar) // Skip nullptr (dummy scope) |
| { |
| ScopeType scopeType = FrameDisplay::GetScopeType(currentScopeObject); |
| switch(scopeType) |
| { |
| case ScopeType_ActivationObject: |
| pVarWalkers->Add(Anew(arena, ObjectVariablesWalker, pFrame, currentScopeObject, UIGroupType_Scope, !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowLexicalThis), !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowSuperReference))); |
| break; |
| case ScopeType_SlotArray: |
| pVarWalkers->Add(Anew(arena, SlotArrayVariablesWalker, pFrame, currentScopeObject, UIGroupType_Scope, !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowLexicalThis), !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowSuperReference))); |
| break; |
| case ScopeType_WithScope: |
| if( (frameWalkerFlags & FrameWalkerFlags::FW_EnumWithScopeAlso) == FrameWalkerFlags::FW_EnumWithScopeAlso) |
| { |
| RecyclableObjectWalker* withScopeWalker = Anew(arena, RecyclableObjectWalker, pFrame->GetScriptContext(), currentScopeObject); |
| pVarWalkers->Add(Anew(arena, DiagScopeVariablesWalker, pFrame, currentScopeObject, withScopeWalker)); |
| } |
| break; |
| default: |
| Assert(false); |
| } |
| } |
| } |
| |
| // No need to add global properties if this is a global function, as it is already done above. |
| if (!shouldAddGlobalItemsDirectly && !dontAddGlobalsDirectly) |
| { |
| pVarWalkers->Add(Anew(arena, RootObjectVariablesWalker, pFrame, pFrame->GetRootObject(), UIGroupType_Globals)); |
| } |
| } |
| } |
| |
| BOOL LocalsWalker::CreateArgumentsObject(ResolvedObject* pResolvedObject) |
| { |
| Assert(pResolvedObject); |
| Assert(pResolvedObject->scriptContext); |
| |
| Assert(hasUserNotDefinedArguments); |
| |
| pResolvedObject->name = _u("arguments"); |
| pResolvedObject->propId = Js::PropertyIds::arguments; |
| pResolvedObject->typeId = TypeIds_Arguments; |
| |
| Js::FunctionBody *pFBody = pFrame->GetJavascriptFunction()->GetFunctionBody(); |
| Assert(pFBody); |
| |
| pResolvedObject->obj = pFrame->GetArgumentsObject(); |
| if (pResolvedObject->obj == nullptr) |
| { |
| pResolvedObject->obj = pFrame->CreateHeapArguments(); |
| Assert(pResolvedObject->obj); |
| |
| pResolvedObject->objectDisplay = Anew(pFrame->GetArena(), RecyclableArgumentsObjectDisplay, pResolvedObject, this); |
| ExpandArgumentsObject(pResolvedObject->objectDisplay); |
| } |
| |
| pResolvedObject->address = Anew(GetArenaFromContext(pResolvedObject->scriptContext), |
| RecyclableObjectAddress, |
| pResolvedObject->scriptContext->GetGlobalObject(), |
| Js::PropertyIds::arguments, |
| pResolvedObject->obj, |
| false /*isInDeadZone*/); |
| |
| return TRUE; |
| } |
| |
| BOOL LocalsWalker::Get(int i, ResolvedObject* pResolvedObject) |
| { |
| if (i >= (int)totalLocalsCount) |
| { |
| return FALSE; |
| } |
| |
| pResolvedObject->scriptContext = pFrame->GetScriptContext(); |
| |
| if (VariableWalkerBase::GetExceptionObject(i, pFrame, pResolvedObject)) |
| { |
| return TRUE; |
| } |
| |
| #ifdef ENABLE_MUTATION_BREAKPOINT |
| // Pending mutation display should be before any return value |
| if (VariableWalkerBase::GetBreakMutationBreakpointValue(i, pFrame, pResolvedObject)) |
| { |
| return TRUE; |
| } |
| #endif |
| |
| if (VariableWalkerBase::GetReturnedValue(i, pFrame, pResolvedObject)) |
| { |
| return TRUE; |
| } |
| |
| if (hasUserNotDefinedArguments) |
| { |
| if (i == 0) |
| { |
| return CreateArgumentsObject(pResolvedObject); |
| } |
| i--; |
| } |
| |
| if (!pVarWalkers || pVarWalkers->Count() == 0) |
| { |
| return FALSE; |
| } |
| |
| // In the case of not making groups, all variables will be arranged |
| // as one int32 list in the locals window. |
| if (!ShouldMakeGroups()) |
| { |
| for (int j = 0; j < pVarWalkers->Count(); j++) |
| { |
| int count = pVarWalkers->Item(j)->GetChildrenCount(); |
| if (i < count) |
| { |
| return pVarWalkers->Item(j)->Get(i, pResolvedObject); |
| } |
| i-= count; |
| } |
| |
| Assert(FALSE); |
| return FALSE; |
| } |
| |
| int startScopeIndex = 0; |
| |
| // Need to determine what range of local variables we're in for the requested index. |
| // Non-grouped local variables are organized with reg slot coming first, then followed by |
| // scope slot/activation object variables. Catch and with variables follow next |
| // and group variables are stored last which come from upper scopes that |
| // are accessed in this function (those passed down as part of a closure). |
| // Note that all/any/none of these walkers may be present. |
| // Example variable layout: |
| // [0-2] - Reg slot vars. |
| // [3-4] - Scope slot array vars. |
| // [5-8] - Global vars (stored on the global object as properties). |
| for (int j = 0; j < pVarWalkers->Count(); ++j) |
| { |
| VariableWalkerBase *variableWalker = pVarWalkers->Item(j); |
| if (!variableWalker->IsInGroup()) |
| { |
| int count = variableWalker->GetChildrenCount(); |
| |
| if (i < count) |
| { |
| return variableWalker->Get(i, pResolvedObject); |
| } |
| i-= count; |
| startScopeIndex++; |
| } |
| else |
| { |
| // We've finished with all walkers for the current locals level so |
| // break out in order to handle the groups. |
| break; |
| } |
| } |
| |
| // Handle groups. |
| Assert((i + startScopeIndex) < pVarWalkers->Count()); |
| VariableWalkerBase *variableWalker = pVarWalkers->Item(i + startScopeIndex); |
| return variableWalker->GetGroupObject(pResolvedObject); |
| } |
| |
| bool LocalsWalker::ShouldInsertFakeArguments() |
| { |
| JavascriptFunction* func = pFrame->GetJavascriptFunction(); |
| if (func->IsScriptFunction() |
| && !func->GetFunctionBody()->GetUtf8SourceInfo()->GetIsLibraryCode() |
| && !func->GetFunctionBody()->GetIsGlobalFunc()) |
| { |
| bool isConst = false; |
| hasUserNotDefinedArguments = (nullptr == FindPropertyAddress(PropertyIds::arguments, false /*walkers on the current frame*/, isConst)); |
| } |
| return hasUserNotDefinedArguments; |
| } |
| |
| uint32 LocalsWalker::GetChildrenCount() |
| { |
| if (totalLocalsCount == 0) |
| { |
| if (pVarWalkers) |
| { |
| int groupWalkersStartIndex = 0; |
| for (int i = 0; i < pVarWalkers->Count(); i++) |
| { |
| VariableWalkerBase* variableWalker = pVarWalkers->Item(i); |
| |
| // In the case of making groups, we want to include any variables that aren't |
| // part of a group as part of the local variable count. |
| if (!ShouldMakeGroups() || !variableWalker->IsInGroup()) |
| { |
| ++groupWalkersStartIndex; |
| totalLocalsCount += variableWalker->GetChildrenCount(); |
| } |
| } |
| |
| // Add on the number of groups to display in locals |
| // (group walkers come after function local walkers). |
| totalLocalsCount += (pVarWalkers->Count() - groupWalkersStartIndex); |
| } |
| |
| if (VariableWalkerBase::HasExceptionObject(pFrame)) |
| { |
| totalLocalsCount++; |
| } |
| |
| #ifdef ENABLE_MUTATION_BREAKPOINT |
| totalLocalsCount += VariableWalkerBase::GetBreakMutationBreakpointsCount(pFrame); |
| #endif |
| totalLocalsCount += VariableWalkerBase::GetReturnedValueCount(pFrame); |
| |
| // Check if needed to add fake arguments. |
| if (ShouldInsertFakeArguments()) |
| { |
| // In this case we need to create arguments object explicitly. |
| totalLocalsCount++; |
| } |
| } |
| return totalLocalsCount; |
| } |
| |
| uint32 LocalsWalker::GetLocalVariablesCount() |
| { |
| uint32 localsCount = 0; |
| if (pVarWalkers) |
| { |
| for (int i = 0; i < pVarWalkers->Count(); i++) |
| { |
| VariableWalkerBase* variableWalker = pVarWalkers->Item(i); |
| |
| // In the case of making groups, we want to include any variables that aren't |
| // part of a group as part of the local variable count. |
| if (!ShouldMakeGroups() || !variableWalker->IsInGroup()) |
| { |
| localsCount += variableWalker->GetChildrenCount(); |
| } |
| } |
| } |
| return localsCount; |
| } |
| |
| BOOL LocalsWalker::GetLocal(int i, ResolvedObject* pResolvedObject) |
| { |
| if (!pVarWalkers || pVarWalkers->Count() == 0) |
| { |
| return FALSE; |
| } |
| |
| for (int j = 0; j < pVarWalkers->Count(); ++j) |
| { |
| VariableWalkerBase *variableWalker = pVarWalkers->Item(j); |
| |
| if (!ShouldMakeGroups() || !variableWalker->IsInGroup()) |
| { |
| int count = variableWalker->GetChildrenCount(); |
| |
| if (i < count) |
| { |
| return variableWalker->Get(i, pResolvedObject); |
| } |
| i -= count; |
| } |
| else |
| { |
| // We've finished with all walkers for the current locals level so |
| // break out in order to handle the groups. |
| break; |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| BOOL LocalsWalker::GetGroupObject(Js::UIGroupType uiGroupType, int i, ResolvedObject* pResolvedObject) |
| { |
| if (pVarWalkers) |
| { |
| int scopeCount = 0; |
| for (int j = 0; j < pVarWalkers->Count(); j++) |
| { |
| VariableWalkerBase* variableWalker = pVarWalkers->Item(j); |
| |
| if (variableWalker->groupType == uiGroupType) |
| { |
| scopeCount++; |
| if (i < scopeCount) |
| { |
| return variableWalker->GetGroupObject(pResolvedObject); |
| } |
| } |
| } |
| } |
| return FALSE; |
| } |
| |
| BOOL LocalsWalker::GetScopeObject(int i, ResolvedObject* pResolvedObject) |
| { |
| return this->GetGroupObject(Js::UIGroupType::UIGroupType_Scope, i, pResolvedObject); |
| } |
| |
| BOOL LocalsWalker::GetGlobalsObject(ResolvedObject* pResolvedObject) |
| { |
| int i = 0; |
| return this->GetGroupObject(Js::UIGroupType::UIGroupType_Globals, i, pResolvedObject); |
| } |
| |
| /*static*/ |
| DebuggerScope * LocalsWalker::GetScopeWhenHaltAtFormals(DiagStackFrame* frame) |
| { |
| Js::ScopeObjectChain * scopeObjectChain = frame->GetJavascriptFunction()->GetFunctionBody()->GetScopeObjectChain(); |
| |
| if (scopeObjectChain != nullptr && scopeObjectChain->pScopeChain != nullptr) |
| { |
| for (int i = 0; i < scopeObjectChain->pScopeChain->Count(); i++) |
| { |
| Js::DebuggerScope * scope = scopeObjectChain->pScopeChain->Item(i); |
| if (scope->IsParamScope()) |
| { |
| return scope; |
| } |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| // Gets an adjusted offset for the current bytecode location based on which stack frame we're in. |
| // If we're in the top frame (leaf node), then the byte code offset should remain as is, to reflect |
| // the current position of the instruction pointer. If we're not in the top frame, we need to subtract |
| // 1 as the byte code location will be placed at the next statement to be executed at the top frame. |
| // In the case of block scoping, this is an inaccurate location for viewing variables since the next |
| // statement could be beyond the current block scope. For inspection, we want to remain in the |
| // current block that the function was called from. |
| // An example is this: |
| // function foo() { ... } // Frame 0 (with breakpoint inside) |
| // function bar() { // Frame 1 |
| // { |
| // let a = 0; |
| // foo(); // <-- Inspecting here, foo is already evaluated. |
| // } |
| // foo(); // <-- Byte code offset is now here, so we need to -1 to get back in the block scope. |
| int LocalsWalker::GetAdjustedByteCodeOffset(DiagStackFrame* frame) |
| { |
| int offset = frame->GetByteCodeOffset(); |
| if (!frame->IsTopFrame() && frame->IsInterpreterFrame()) |
| { |
| // Native frames are already adjusted so just need to adjust interpreted |
| // frames that are not the top frame. |
| --offset; |
| } |
| |
| return offset; |
| } |
| |
| /*static*/ |
| DWORD LocalsWalker::GetCurrentFramesLocalsType(DiagStackFrame* frame) |
| { |
| Assert(frame); |
| |
| FunctionBody *pFBody = frame->GetJavascriptFunction()->GetFunctionBody(); |
| Assert(pFBody); |
| |
| DWORD localType = FramesLocalType::LocalType_None; |
| |
| if (pFBody->GetFrameDisplayRegister() != 0) |
| { |
| if (pFBody->GetObjectRegister() != 0) |
| { |
| // current scope is activation object |
| localType = FramesLocalType::LocalType_InObject; |
| } |
| else |
| { |
| if (pFBody->scopeSlotArraySize > 0) |
| { |
| localType = FramesLocalType::LocalType_InSlot; |
| } |
| } |
| } |
| |
| if (pFBody->GetPropertyIdOnRegSlotsContainer() && pFBody->GetPropertyIdOnRegSlotsContainer()->length > 0) |
| { |
| localType |= FramesLocalType::LocalType_Reg; |
| } |
| |
| return localType; |
| } |
| |
| IDiagObjectAddress * LocalsWalker::FindPropertyAddress(PropertyId propId, bool& isConst) |
| { |
| return FindPropertyAddress(propId, true, isConst); |
| } |
| |
| IDiagObjectAddress * LocalsWalker::FindPropertyAddress(PropertyId propId, bool enumerateGroups, bool& isConst) |
| { |
| isConst = false; |
| if (propId == PropertyIds::arguments && hasUserNotDefinedArguments) |
| { |
| ResolvedObject resolveObject; |
| resolveObject.scriptContext = pFrame->GetScriptContext(); |
| if (CreateArgumentsObject(&resolveObject)) |
| { |
| return resolveObject.address; |
| } |
| } |
| |
| if (pVarWalkers) |
| { |
| for (int i = 0; i < pVarWalkers->Count(); i++) |
| { |
| VariableWalkerBase *pVarWalker = pVarWalkers->Item(i); |
| if (!enumerateGroups && !pVarWalker->IsWalkerForCurrentFrame()) |
| { |
| continue; |
| } |
| |
| IDiagObjectAddress *address = pVarWalkers->Item(i)->FindPropertyAddress(propId, isConst); |
| if (address != nullptr) |
| { |
| return address; |
| } |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| void LocalsWalker::ExpandArgumentsObject(IDiagObjectModelDisplay * argumentsDisplay) |
| { |
| Assert(argumentsDisplay != nullptr); |
| |
| WeakArenaReference<Js::IDiagObjectModelWalkerBase>* argumentsObjectWalkerRef = argumentsDisplay->CreateWalker(); |
| Assert(argumentsObjectWalkerRef != nullptr); |
| |
| IDiagObjectModelWalkerBase * walker = argumentsObjectWalkerRef->GetStrongReference(); |
| int count = (int)walker->GetChildrenCount(); |
| Js::ResolvedObject tempResolvedObj; |
| for (int i = 0; i < count; i++) |
| { |
| walker->Get(i, &tempResolvedObj); |
| } |
| argumentsObjectWalkerRef->ReleaseStrongReference(); |
| HeapDelete(argumentsObjectWalkerRef); |
| } |
| |
| //-------------------------- |
| // LocalObjectAddressForSlot |
| |
| |
| LocalObjectAddressForSlot::LocalObjectAddressForSlot(ScopeSlots _pSlotArray, int _slotIndex, Js::Var _value) |
| : slotArray(_pSlotArray), |
| slotIndex(_slotIndex), |
| value(_value) |
| { |
| } |
| |
| BOOL LocalObjectAddressForSlot::Set(Var updateObject) |
| { |
| if (IsInDeadZone()) |
| { |
| AssertMsg(FALSE, "Should not be able to set the value of a slot in a dead zone."); |
| return FALSE; |
| } |
| |
| slotArray.Set(slotIndex, updateObject); |
| return TRUE; |
| } |
| |
| Var LocalObjectAddressForSlot::GetValue(BOOL fUpdated) |
| { |
| if (!fUpdated || IsInDeadZone()) |
| { |
| #if DBG |
| if (IsInDeadZone()) |
| { |
| // If we're in a dead zone, the value will be the |
| // [Uninitialized block variable] string. |
| Assert(JavascriptString::Is(value)); |
| } |
| #endif // DBG |
| |
| return value; |
| } |
| |
| return slotArray.Get(slotIndex); |
| } |
| |
| BOOL LocalObjectAddressForSlot::IsInDeadZone() const |
| { |
| Var value = slotArray.Get(slotIndex); |
| if (!RecyclableObject::Is(value)) |
| { |
| return FALSE; |
| } |
| |
| RecyclableObject* obj = RecyclableObject::FromVar(value); |
| ScriptContext* scriptContext = obj->GetScriptContext(); |
| return scriptContext->IsUndeclBlockVar(obj) ? TRUE : FALSE; |
| } |
| |
| //-------------------------- |
| // LocalObjectAddressForSlot |
| |
| |
| LocalObjectAddressForRegSlot::LocalObjectAddressForRegSlot(DiagStackFrame* _pFrame, RegSlot _regSlot, Js::Var _value) |
| : pFrame(_pFrame), |
| regSlot(_regSlot), |
| value(_value) |
| { |
| } |
| |
| BOOL LocalObjectAddressForRegSlot::IsInDeadZone() const |
| { |
| return regSlot == Js::Constants::NoRegister; |
| } |
| |
| BOOL LocalObjectAddressForRegSlot::Set(Var updateObject) |
| { |
| Assert(pFrame); |
| |
| if (IsInDeadZone()) |
| { |
| AssertMsg(FALSE, "Should not be able to set the value of a register in a dead zone."); |
| return FALSE; |
| } |
| |
| pFrame->SetRegValue(regSlot, updateObject); |
| |
| return TRUE; |
| } |
| |
| Var LocalObjectAddressForRegSlot::GetValue(BOOL fUpdated) |
| { |
| if (!fUpdated || IsInDeadZone()) |
| { |
| #if DBG |
| if (IsInDeadZone()) |
| { |
| // If we're in a dead zone, the value will be the |
| // [Uninitialized block variable] string. |
| Assert(JavascriptString::Is(value)); |
| } |
| #endif // DBG |
| |
| return value; |
| } |
| |
| Assert(pFrame); |
| return pFrame->GetRegValue(regSlot); |
| } |
| |
| // |
| // CatchScopeWalker |
| |
| BOOL CatchScopeWalker::Get(int i, ResolvedObject* pResolvedObject) |
| { |
| Assert(pResolvedObject); |
| |
| Assert(pFrame); |
| pResolvedObject->scriptContext = pFrame->GetScriptContext(); |
| Assert(i < (int)GetChildrenCount()); |
| Js::DebuggerScopeProperty scopeProperty = debuggerScope->scopeProperties->Item(i); |
| |
| pResolvedObject->propId = scopeProperty.propId; |
| |
| const Js::PropertyRecord* propertyRecord = pResolvedObject->scriptContext->GetPropertyName(pResolvedObject->propId); |
| |
| // TODO: If this is a symbol-keyed property, we should indicate that in the name - "Symbol (description)" |
| pResolvedObject->name = propertyRecord->GetBuffer(); |
| |
| FetchValueAndAddress(scopeProperty, &pResolvedObject->obj, &pResolvedObject->address); |
| |
| Assert(pResolvedObject->obj); |
| |
| pResolvedObject->typeId = JavascriptOperators::GetTypeId(pResolvedObject->obj); |
| pResolvedObject->objectDisplay = Anew(pFrame->GetArena(), RecyclableObjectDisplay, pResolvedObject); |
| |
| return TRUE; |
| } |
| |
| uint32 CatchScopeWalker::GetChildrenCount() |
| { |
| return debuggerScope->scopeProperties->Count(); |
| } |
| |
| void CatchScopeWalker::FetchValueAndAddress(DebuggerScopeProperty &scopeProperty, _Out_opt_ Var *pValue, _Out_opt_ IDiagObjectAddress ** ppAddress) |
| { |
| Assert(pValue != nullptr || ppAddress != nullptr); |
| |
| ArenaAllocator* arena = pFrame->GetArena(); |
| Var outValue; |
| IDiagObjectAddress * pAddress = nullptr; |
| |
| ScriptContext* scriptContext = pFrame->GetScriptContext(); |
| if (debuggerScope->scopeType == Js::DiagCatchScopeInObject) |
| { |
| Var obj = pFrame->GetInnerScopeFromRegSlot(debuggerScope->GetLocation()); |
| Assert(RecyclableObject::Is(obj)); |
| |
| outValue = RecyclableObjectWalker::GetObject(RecyclableObject::FromVar(obj), RecyclableObject::FromVar(obj), scopeProperty.propId, scriptContext); |
| bool isInDeadZone = scriptContext->IsUndeclBlockVar(outValue); |
| if (isInDeadZone) |
| { |
| outValue = scriptContext->GetLibrary()->GetDebuggerDeadZoneBlockVariableString(); |
| |
| } |
| pAddress = Anew(arena, RecyclableObjectAddress, obj, scopeProperty.propId, outValue, isInDeadZone); |
| } |
| else |
| { |
| outValue = pFrame->GetRegValue(scopeProperty.location); |
| bool isInDeadZone = scriptContext->IsUndeclBlockVar(outValue); |
| if (isInDeadZone) |
| { |
| outValue = scriptContext->GetLibrary()->GetDebuggerDeadZoneBlockVariableString(); |
| |
| } |
| pAddress = Anew(arena, LocalObjectAddressForRegSlot, pFrame, scopeProperty.location, outValue); |
| } |
| |
| if (pValue) |
| { |
| *pValue = outValue; |
| } |
| |
| if (ppAddress) |
| { |
| *ppAddress = pAddress; |
| } |
| } |
| |
| IDiagObjectAddress *CatchScopeWalker::FindPropertyAddress(PropertyId _propId, bool& isConst) |
| { |
| isConst = false; |
| IDiagObjectAddress * address = nullptr; |
| auto properties = debuggerScope->scopeProperties; |
| for (int i = 0; i < properties->Count(); i++) |
| { |
| if (properties->Item(i).propId == _propId) |
| { |
| FetchValueAndAddress(properties->Item(i), nullptr, &address); |
| break; |
| } |
| } |
| |
| return address; |
| } |
| |
| //-------------------------- |
| // RecyclableObjectAddress |
| |
| RecyclableObjectAddress::RecyclableObjectAddress(Var _parentObj, Js::PropertyId _propId, Js::Var _value, BOOL _isInDeadZone) |
| : parentObj(_parentObj), |
| propId(_propId), |
| value(_value), |
| isInDeadZone(_isInDeadZone) |
| { |
| parentObj = ((RecyclableObject*)parentObj)->GetThisObjectOrUnWrap(); |
| } |
| |
| BOOL RecyclableObjectAddress::IsInDeadZone() const |
| { |
| return isInDeadZone; |
| } |
| |
| BOOL RecyclableObjectAddress::Set(Var updateObject) |
| { |
| if (Js::RecyclableObject::Is(parentObj)) |
| { |
| Js::RecyclableObject* obj = Js::RecyclableObject::FromVar(parentObj); |
| |
| ScriptContext* requestContext = obj->GetScriptContext(); //TODO: real requestContext |
| return Js::JavascriptOperators::SetProperty(obj, obj, propId, updateObject, requestContext); |
| } |
| return FALSE; |
| } |
| |
| BOOL RecyclableObjectAddress::IsWritable() |
| { |
| if (Js::RecyclableObject::Is(parentObj)) |
| { |
| Js::RecyclableObject* obj = Js::RecyclableObject::FromVar(parentObj); |
| |
| return obj->IsWritable(propId); |
| } |
| |
| return TRUE; |
| } |
| |
| Var RecyclableObjectAddress::GetValue(BOOL fUpdated) |
| { |
| if (!fUpdated) |
| { |
| return value; |
| } |
| |
| if (Js::RecyclableObject::Is(parentObj)) |
| { |
| Js::RecyclableObject* obj = Js::RecyclableObject::FromVar(parentObj); |
| |
| ScriptContext* requestContext = obj->GetScriptContext(); |
| Var objValue = nullptr; |
| |
| #if ENABLE_TTD |
| bool suppressGetterForTTDebug = requestContext->GetThreadContext()->IsRuntimeInTTDMode() && requestContext->GetThreadContext()->TTDLog->ShouldDoGetterInvocationSupression(); |
| TTD::TTModeStackAutoPopper suppressModeAutoPopper(requestContext->GetThreadContext()->TTDLog); |
| if(suppressGetterForTTDebug) |
| { |
| suppressModeAutoPopper.PushModeAndSetToAutoPop(TTD::TTDMode::DebuggerSuppressGetter); |
| } |
| #endif |
| |
| if (Js::JavascriptOperators::GetProperty(obj, propId, &objValue, requestContext)) |
| { |
| return objValue; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| //-------------------------- |
| // RecyclableObjectDisplay |
| |
| |
| RecyclableObjectDisplay::RecyclableObjectDisplay(ResolvedObject* resolvedObject, DBGPROP_ATTRIB_FLAGS defaultAttributes) |
| : scriptContext(resolvedObject->scriptContext), |
| instance(resolvedObject->obj), |
| originalInstance(resolvedObject->originalObj != nullptr ? resolvedObject->originalObj : resolvedObject->obj), // If we don't have it set it means originalInstance should point to object itself |
| name(resolvedObject->name), |
| pObjAddress(resolvedObject->address), |
| defaultAttributes(defaultAttributes), |
| propertyId(resolvedObject->propId) |
| { |
| } |
| |
| bool RecyclableObjectDisplay::IsLiteralProperty() const |
| { |
| Assert(this->scriptContext); |
| |
| if (this->propertyId != Constants::NoProperty) |
| { |
| Js::PropertyRecord const * propertyRecord = this->scriptContext->GetThreadContext()->GetPropertyName(this->propertyId); |
| const WCHAR* startOfPropertyName = propertyRecord->GetBuffer(); |
| const WCHAR* endOfIdentifier = this->scriptContext->GetCharClassifier()->SkipIdentifier((LPCOLESTR)propertyRecord->GetBuffer()); |
| return (charcount_t)(endOfIdentifier - startOfPropertyName) == propertyRecord->GetLength(); |
| } |
| else |
| { |
| return true; |
| } |
| } |
| |
| |
| bool RecyclableObjectDisplay::IsSymbolProperty() |
| { |
| Assert(this->scriptContext); |
| |
| if (this->propertyId != Constants::NoProperty) |
| { |
| Js::PropertyRecord const * propertyRecord = this->scriptContext->GetThreadContext()->GetPropertyName(this->propertyId); |
| return propertyRecord->IsSymbol(); |
| } |
| |
| return false; |
| } |
| |
| LPCWSTR RecyclableObjectDisplay::Name() |
| { |
| return name; |
| } |
| |
| LPCWSTR RecyclableObjectDisplay::Type() |
| { |
| LPCWSTR typeStr; |
| |
| if(Js::TaggedInt::Is(instance) || Js::JavascriptNumber::Is(instance)) |
| { |
| typeStr = _u("Number"); |
| } |
| else |
| { |
| Js::RecyclableObject* obj = Js::RecyclableObject::FromVar(instance); |
| |
| StringBuilder<ArenaAllocator>* builder = scriptContext->GetThreadContext()->GetDebugManager()->pCurrentInterpreterLocation->stringBuilder; |
| builder->Reset(); |
| |
| // For the RecyclableObject try to find out the constructor, which will be shown as type for the object. |
| // This case is to handle the user defined function, built in objects have dedicated classes to handle. |
| |
| Var value = nullptr; |
| TypeId typeId = obj->GetTypeId(); |
| if (typeId == TypeIds_Object && GetPropertyWithScriptEnter(obj, obj, PropertyIds::constructor, &value, scriptContext)) |
| { |
| builder->AppendCppLiteral(_u("Object")); |
| if (Js::JavascriptFunction::Is(value)) |
| { |
| Js::JavascriptFunction *pfunction = Js::JavascriptFunction::FromVar(value); |
| // For an odd chance that the constructor wasn't called to create the object. |
| Js::ParseableFunctionInfo *pFuncBody = pfunction->GetFunctionProxy() != nullptr ? pfunction->GetFunctionProxy()->EnsureDeserialized() : nullptr; |
| if (pFuncBody) |
| { |
| const char16* pDisplayName = pFuncBody->GetDisplayName(); |
| if (pDisplayName) |
| { |
| builder->AppendCppLiteral(_u(", (")); |
| builder->AppendSz(pDisplayName); |
| builder->Append(_u(')')); |
| } |
| } |
| } |
| typeStr = builder->Detach(); |
| } |
| else if (obj->GetDiagTypeString(builder, scriptContext)) |
| { |
| typeStr = builder->Detach(); |
| } |
| else |
| { |
| typeStr = _u("Undefined"); |
| } |
| } |
| |
| return typeStr; |
| } |
| |
| Var RecyclableObjectDisplay::GetVarValue(BOOL fUpdated) |
| { |
| if (pObjAddress) |
| { |
| return pObjAddress->GetValue(fUpdated); |
| } |
| return instance; |
| } |
| |
| LPCWSTR RecyclableObjectDisplay::Value(int radix) |
| { |
| LPCWSTR valueStr = _u(""); |
| |
| if(Js::TaggedInt::Is(instance) |
| || Js::JavascriptNumber::Is(instance) |
| || Js::JavascriptNumberObject::Is(instance) |
| || Js::JavascriptOperators::GetTypeId(instance) == TypeIds_Int64Number |
| || Js::JavascriptOperators::GetTypeId(instance) == TypeIds_UInt64Number) |
| { |
| double value; |
| if (Js::TaggedInt::Is(instance)) |
| { |
| value = TaggedInt::ToDouble(instance); |
| } |
| else if (Js::JavascriptNumber::Is(instance)) |
| { |
| value = Js::JavascriptNumber::GetValue(instance); |
| } |
| else if (Js::JavascriptOperators::GetTypeId(instance) == TypeIds_Int64Number) |
| { |
| value = (double)JavascriptInt64Number::FromVar(instance)->GetValue(); |
| } |
| else if (Js::JavascriptOperators::GetTypeId(instance) == TypeIds_UInt64Number) |
| { |
| value = (double)JavascriptUInt64Number::FromVar(instance)->GetValue(); |
| } |
| else |
| { |
| Js::JavascriptNumberObject* numobj = Js::JavascriptNumberObject::FromVar(instance); |
| value = numobj->GetValue(); |
| } |
| |
| // For fractional values, radix is ignored. |
| int32 l = (int32)value; |
| bool isZero = JavascriptNumber::IsZero(value - (double)l); |
| |
| if (radix == 10 || !isZero) |
| { |
| if (Js::JavascriptNumber::IsNegZero(value)) |
| { |
| // In debugger, we wanted to show negative zero explicitly |
| valueStr = _u("-0"); |
| } |
| else |
| { |
| valueStr = Js::JavascriptNumber::ToStringRadix10(value, scriptContext)->GetSz(); |
| } |
| } |
| else if (radix >= 2 && radix <= 36) |
| { |
| if (radix == 16) |
| { |
| if (value < 0) |
| { |
| // On the tools side we show unsigned value. |
| uint32 ul = static_cast<uint32>(static_cast<int32>(value)); // ARM: casting negative value to uint32 gives 0 |
| value = (double)ul; |
| } |
| valueStr = Js::JavascriptString::Concat(scriptContext->GetLibrary()->CreateStringFromCppLiteral(_u("0x")), |
| Js::JavascriptNumber::ToStringRadixHelper(value, radix, scriptContext))->GetSz(); |
| } |
| else |
| { |
| valueStr = Js::JavascriptNumber::ToStringRadixHelper(value, radix, scriptContext)->GetSz(); |
| } |
| } |
| } |
| else |
| { |
| Js::RecyclableObject* obj = Js::RecyclableObject::FromVar(instance); |
| |
| StringBuilder<ArenaAllocator>* builder = scriptContext->GetThreadContext()->GetDebugManager()->pCurrentInterpreterLocation->stringBuilder; |
| builder->Reset(); |
| |
| if (obj->GetDiagValueString(builder, scriptContext)) |
| { |
| valueStr = builder->Detach(); |
| } |
| else |
| { |
| valueStr = _u("undefined"); |
| } |
| } |
| |
| return valueStr; |
| } |
| |
| BOOL RecyclableObjectDisplay::HasChildren() |
| { |
| if (Js::RecyclableObject::Is(instance)) |
| { |
| Js::RecyclableObject* object = Js::RecyclableObject::FromVar(instance); |
| |
| if (JavascriptOperators::IsObject(object)) |
| { |
| if (JavascriptOperators::GetTypeId(object) == TypeIds_HostDispatch) |
| { |
| return TRUE; |
| } |
| |
| try |
| { |
| auto funcPtr = [&]() |
| { |
| IGNORE_STACKWALK_EXCEPTION(scriptContext); |
| if (object->CanHaveInterceptors()) |
| { |
| Js::ForInObjectEnumerator enumerator(object, object->GetScriptContext(), /* enumSymbols */ true); |
| Js::PropertyId propertyId; |
| if (enumerator.MoveAndGetNext(propertyId)) |
| { |
| enumerator.Clear(); |
| return TRUE; |
| } |
| } |
| else if (object->GetPropertyCount() > 0 || (JavascriptOperators::GetTypeId(object->GetPrototype()) != TypeIds_Null)) |
| { |
| return TRUE; |
| } |
| |
| return FALSE; |
| }; |
| |
| BOOL autoFuncReturn = FALSE; |
| |
| if (!scriptContext->GetThreadContext()->IsScriptActive()) |
| { |
| BEGIN_JS_RUNTIME_CALL_EX(scriptContext, false) |
| { |
| autoFuncReturn = funcPtr(); |
| } |
| END_JS_RUNTIME_CALL(scriptContext); |
| } |
| else |
| { |
| autoFuncReturn = funcPtr(); |
| } |
| |
| if (autoFuncReturn == TRUE) |
| { |
| return TRUE; |
| } |
| } |
| catch (const JavascriptException& err) |
| { |
| // The For in enumerator can throw an exception and we will use the error object as a child in that case. |
| Var error = err.GetAndClear()->GetThrownObject(scriptContext); |
| if (error != nullptr && Js::JavascriptError::Is(error)) |
| { |
| return TRUE; |
| } |
| return FALSE; |
| } |
| } |
| } |
| |
| return FALSE; |
| } |
| |
| BOOL RecyclableObjectDisplay::Set(Var updateObject) |
| { |
| if (pObjAddress) |
| { |
| return pObjAddress->Set(updateObject); |
| } |
| return FALSE; |
| } |
| |
| DBGPROP_ATTRIB_FLAGS RecyclableObjectDisplay::GetTypeAttribute() |
| { |
| DBGPROP_ATTRIB_FLAGS flag = defaultAttributes; |
| |
| if (Js::RecyclableObject::Is(instance)) |
| { |
| if (instance == scriptContext->GetLibrary()->GetDebuggerDeadZoneBlockVariableString()) |
| { |
| flag |= DBGPROP_ATTRIB_VALUE_IS_INVALID; |
| } |
| else if (JavascriptOperators::GetTypeId(instance) == TypeIds_Function) |
| { |
| flag |= DBGPROP_ATTRIB_VALUE_IS_METHOD; |
| } |
| else if (JavascriptOperators::GetTypeId(instance) == TypeIds_String |
| || JavascriptOperators::GetTypeId(instance) == TypeIds_StringObject) |
| { |
| flag |= DBGPROP_ATTRIB_VALUE_IS_RAW_STRING; |
| } |
| } |
| |
| auto checkWriteableFunction = [&]() |
| { |
| if (pObjAddress && !pObjAddress->IsWritable()) |
| { |
| flag |= DBGPROP_ATTRIB_VALUE_READONLY; |
| } |
| }; |
| |
| if (!scriptContext->GetThreadContext()->IsScriptActive()) |
| { |
| BEGIN_JS_RUNTIME_CALL_EX(scriptContext, false); |
| { |
| IGNORE_STACKWALK_EXCEPTION(scriptContext); |
| checkWriteableFunction(); |
| } |
| END_JS_RUNTIME_CALL(scriptContext); |
| } |
| else |
| { |
| checkWriteableFunction(); |
| } |
| // TODO : need to identify Events explicitly for fastDOM |
| |
| return flag; |
| } |
| |
| |
| /* static */ |
| BOOL RecyclableObjectDisplay::GetPropertyWithScriptEnter(RecyclableObject* originalInstance, RecyclableObject* instance, PropertyId propertyId, Var* value, ScriptContext* scriptContext) |
| { |
| BOOL retValue = FALSE; |
| |
| #if ENABLE_TTD |
| bool suppressGetterForTTDebug = scriptContext->GetThreadContext()->IsRuntimeInTTDMode() && scriptContext->GetThreadContext()->TTDLog->ShouldDoGetterInvocationSupression(); |
| TTD::TTModeStackAutoPopper suppressModeAutoPopper(scriptContext->GetThreadContext()->TTDLog); |
| if(suppressGetterForTTDebug) |
| { |
| suppressModeAutoPopper.PushModeAndSetToAutoPop(TTD::TTDMode::DebuggerSuppressGetter); |
| } |
| #endif |
| |
| if(!scriptContext->GetThreadContext()->IsScriptActive()) |
| { |
| BEGIN_JS_RUNTIME_CALL_EX(scriptContext, false) |
| { |
| IGNORE_STACKWALK_EXCEPTION(scriptContext); |
| retValue = Js::JavascriptOperators::GetProperty(originalInstance, instance, propertyId, value, scriptContext); |
| } |
| END_JS_RUNTIME_CALL(scriptContext); |
| } |
| else |
| { |
| retValue = Js::JavascriptOperators::GetProperty(originalInstance, instance, propertyId, value, scriptContext); |
| } |
| |
| return retValue; |
| } |
| |
| |
| WeakArenaReference<IDiagObjectModelWalkerBase>* RecyclableObjectDisplay::CreateWalker() |
| { |
| return CreateAWalker<RecyclableObjectWalker>(scriptContext, instance, originalInstance); |
| } |
| |
| StringBuilder<ArenaAllocator>* RecyclableObjectDisplay::GetStringBuilder() |
| { |
| return scriptContext->GetThreadContext()->GetDebugManager()->pCurrentInterpreterLocation->stringBuilder; |
| } |
| |
| PropertyId RecyclableObjectDisplay::GetPropertyId() const |
| { |
| return this->propertyId; |
| } |
| |
| // ------------------------------------ |
| // RecyclableObjectWalker |
| |
| RecyclableObjectWalker::RecyclableObjectWalker(ScriptContext* _scriptContext, Var _slot) |
| : scriptContext(_scriptContext), |
| instance(_slot), |
| originalInstance(_slot), |
| pMembersList(nullptr), |
| innerArrayObjectWalker(nullptr), |
| fakeGroupObjectWalkerList(nullptr) |
| { |
| } |
| |
| RecyclableObjectWalker::RecyclableObjectWalker(ScriptContext* _scriptContext, Var _slot, Var _originalInstance) |
| : scriptContext(_scriptContext), |
| instance(_slot), |
| originalInstance(_originalInstance), |
| pMembersList(nullptr), |
| innerArrayObjectWalker(nullptr), |
| fakeGroupObjectWalkerList(nullptr) |
| { |
| } |
| |
| BOOL RecyclableObjectWalker::Get(int index, ResolvedObject* pResolvedObject) |
| { |
| AssertMsg(pResolvedObject, "Bad usage of RecyclableObjectWalker::Get"); |
| |
| int fakeObjCount = fakeGroupObjectWalkerList ? fakeGroupObjectWalkerList->Count() : 0; |
| int arrayItemCount = innerArrayObjectWalker ? innerArrayObjectWalker->GetChildrenCount() : 0; |
| |
| if (index < 0 || !pMembersList || index >= (pMembersList->Count() + arrayItemCount + fakeObjCount)) |
| { |
| return FALSE; |
| } |
| |
| int nonArrayElementCount = Js::RecyclableObject::Is(instance) ? pMembersList->Count() : 0; |
| |
| // First the virtual groups |
| if (index < fakeObjCount) |
| { |
| Assert(fakeGroupObjectWalkerList); |
| return fakeGroupObjectWalkerList->Item(index)->GetGroupObject(pResolvedObject); |
| } |
| |
| index -= fakeObjCount; |
| |
| if (index < nonArrayElementCount) |
| { |
| Assert(Js::RecyclableObject::Is(instance)); |
| |
| pResolvedObject->propId = pMembersList->Item(index)->propId; |
| |
| if (pResolvedObject->propId == Js::Constants::NoProperty || Js::IsInternalPropertyId(pResolvedObject->propId)) |
| { |
| Assert(FALSE); |
| return FALSE; |
| } |
| |
| Js::DebuggerPropertyDisplayInfo* displayInfo = pMembersList->Item(index); |
| const Js::PropertyRecord* propertyRecord = scriptContext->GetPropertyName(pResolvedObject->propId); |
| |
| pResolvedObject->name = propertyRecord->GetBuffer(); |
| pResolvedObject->obj = displayInfo->aVar; |
| Assert(pResolvedObject->obj); |
| |
| pResolvedObject->scriptContext = scriptContext; |
| pResolvedObject->typeId = JavascriptOperators::GetTypeId(pResolvedObject->obj); |
| |
| pResolvedObject->address = Anew(GetArenaFromContext(scriptContext), |
| RecyclableObjectAddress, |
| instance, |
| pResolvedObject->propId, |
| pResolvedObject->obj, |
| displayInfo->IsInDeadZone() ? TRUE : FALSE); |
| |
| pResolvedObject->isConst = displayInfo->IsConst(); |
| |
| return TRUE; |
| } |
| |
| index -= nonArrayElementCount; |
| |
| if (index < arrayItemCount) |
| { |
| Assert(innerArrayObjectWalker); |
| return innerArrayObjectWalker->Get(index, pResolvedObject); |
| } |
| |
| Assert(false); |
| return FALSE; |
| } |
| |
| void RecyclableObjectWalker::EnsureFakeGroupObjectWalkerList() |
| { |
| if (fakeGroupObjectWalkerList == nullptr) |
| { |
| ArenaAllocator *arena = GetArenaFromContext(scriptContext); |
| fakeGroupObjectWalkerList = JsUtil::List<IDiagObjectModelWalkerBase *, ArenaAllocator>::New(arena); |
| } |
| } |
| |
| IDiagObjectAddress *RecyclableObjectWalker::FindPropertyAddress(PropertyId propertyId, bool& isConst) |
| { |
| GetChildrenCount(); // Ensure to populate members |
| |
| if (pMembersList != nullptr) |
| { |
| for (int i = 0; i < pMembersList->Count(); i++) |
| { |
| DebuggerPropertyDisplayInfo *pair = pMembersList->Item(i); |
| Assert(pair); |
| if (pair->propId == propertyId) |
| { |
| isConst = pair->IsConst(); |
| return Anew(GetArenaFromContext(scriptContext), |
| RecyclableObjectAddress, |
| instance, |
| propertyId, |
| pair->aVar, |
| pair->IsInDeadZone() ? TRUE : FALSE); |
| } |
| } |
| } |
| |
| // Following is for "with object" scope lookup. We may have members in [Methods] group or prototype chain that need to |
| // be exposed to expression evaluation. |
| if (fakeGroupObjectWalkerList != nullptr) |
| { |
| // WARNING: Following depends on [Methods] group being before [prototype] group. We need to check local [Methods] group |
| // first for local properties before going to prototype chain. |
| for (int i = 0; i < fakeGroupObjectWalkerList->Count(); i++) |
| { |
| IDiagObjectAddress* address = fakeGroupObjectWalkerList->Item(i)->FindPropertyAddress(propertyId, isConst); |
| if (address != nullptr) |
| { |
| return address; |
| } |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| uint32 RecyclableObjectWalker::GetChildrenCount() |
| { |
| if (pMembersList == nullptr) |
| { |
| ArenaAllocator *arena = GetArenaFromContext(scriptContext); |
| |
| pMembersList = JsUtil::List<DebuggerPropertyDisplayInfo *, ArenaAllocator>::New(arena); |
| |
| RecyclableMethodsGroupWalker *pMethodsGroupWalker = nullptr; |
| |
| if (Js::RecyclableObject::Is(instance)) |
| { |
| Js::RecyclableObject* object = Js::RecyclableObject::FromVar(instance); |
| // If we are walking a prototype, we'll use its instance for property names enumeration, but originalInstance to get values |
| Js::RecyclableObject* originalObject = (originalInstance != nullptr) ? Js::RecyclableObject::FromVar(originalInstance) : object; |
| const Js::TypeId typeId = JavascriptOperators::GetTypeId(instance); |
| |
| if (JavascriptOperators::IsObject(object)) |
| { |
| if (object->CanHaveInterceptors() || JavascriptOperators::GetTypeId(object) == TypeIds_Proxy) |
| { |
| try |
| { |
| ScriptContext * objectContext = object->GetScriptContext(); |
| JavascriptStaticEnumerator enumerator; |
| if (object->GetEnumerator(&enumerator, EnumeratorFlags::EnumNonEnumerable | EnumeratorFlags::EnumSymbols, objectContext)) |
| { |
| Js::PropertyId propertyId; |
| JavascriptString * obj; |
| |
| while ((obj = enumerator.MoveAndGetNext(propertyId)) != nullptr) |
| { |
| if (propertyId == Constants::NoProperty) |
| { |
| if (VirtualTableInfo<Js::PropertyString>::HasVirtualTable(obj)) |
| { |
| // If we have a property string, it is assumed that the propertyId is being |
| // kept alive with the object |
| PropertyString * propertyString = (PropertyString *)obj; |
| propertyId = propertyString->GetPropertyRecord()->GetPropertyId(); |
| } |
| else |
| { |
| const PropertyRecord* propertyRecord; |
| objectContext->GetOrAddPropertyRecord(obj->GetSz(), obj->GetLength(), &propertyRecord); |
| propertyId = propertyRecord->GetPropertyId(); |
| } |
| } |
| // MoveAndGetNext shouldn't return an internal property id |
| Assert(!Js::IsInternalPropertyId(propertyId)); |
| |
| uint32 indexVal; |
| Var varValue; |
| if (objectContext->IsNumericPropertyId(propertyId, &indexVal) && object->GetItem(object, indexVal, &varValue, objectContext)) |
| { |
| InsertItem(propertyId, false /*isConst*/, false /*isUnscoped*/, varValue, &pMethodsGroupWalker, true /*shouldPinProperty*/); |
| } |
| else |
| { |
| InsertItem(originalObject, object, propertyId, false /*isConst*/, false /*isUnscoped*/, &pMethodsGroupWalker, true /*shouldPinProperty*/); |
| } |
| } |
| } |
| } |
| catch (const JavascriptException& err) |
| { |
| Var error = err.GetAndClear()->GetThrownObject(scriptContext); |
| if (error != nullptr && Js::JavascriptError::Is(error)) |
| { |
| Js::PropertyId propertyId = scriptContext->GetOrAddPropertyIdTracked(_u("{error}")); |
| InsertItem(propertyId, false /*isConst*/, false /*isUnscoped*/, error, &pMethodsGroupWalker); |
| } |
| } |
| |
| if (typeId == TypeIds_Proxy) |
| { |
| // Provide [Proxy] group object |
| EnsureFakeGroupObjectWalkerList(); |
| |
| JavascriptProxy* proxy = JavascriptProxy::FromVar(object); |
| RecyclableProxyObjectWalker* proxyWalker = Anew(arena, RecyclableProxyObjectWalker, scriptContext, proxy); |
| fakeGroupObjectWalkerList->Add(proxyWalker); |
| } |
| // If current object has internal proto object then provide [prototype] group object. |
| if (JavascriptOperators::GetTypeId(object->GetPrototype()) != TypeIds_Null) |
| { |
| // Has [prototype] object. |
| EnsureFakeGroupObjectWalkerList(); |
| |
| RecyclableProtoObjectWalker *pProtoWalker = Anew(arena, RecyclableProtoObjectWalker, scriptContext, instance, (originalInstance == nullptr) ? instance : originalInstance); |
| fakeGroupObjectWalkerList->Add(pProtoWalker); |
| } |
| } |
| else |
| { |
| RecyclableObject* wrapperObject = nullptr; |
| if (JavascriptOperators::GetTypeId(object) == TypeIds_WithScopeObject) |
| { |
| wrapperObject = object; |
| object = object->GetThisObjectOrUnWrap(); |
| } |
| |
| int count = object->GetPropertyCount(); |
| |
| for (int i = 0; i < count; i++) |
| { |
| Js::PropertyId propertyId = object->GetPropertyId((PropertyIndex)i); |
| bool isUnscoped = false; |
| if (wrapperObject && JavascriptOperators::IsPropertyUnscopable(object, propertyId)) |
| { |
| isUnscoped = true; |
| } |
| if (propertyId != Js::Constants::NoProperty && !Js::IsInternalPropertyId(propertyId)) |
| { |
| InsertItem(originalObject, object, propertyId, false /*isConst*/, isUnscoped, &pMethodsGroupWalker); |
| } |
| } |
| |
| if (CONFIG_FLAG(EnumerateSpecialPropertiesInDebugger)) |
| { |
| count = object->GetSpecialPropertyCount(); |
| PropertyId const * specialPropertyIds = object->GetSpecialPropertyIds(); |
| for (int i = 0; i < count; i++) |
| { |
| Js::PropertyId propertyId = specialPropertyIds[i]; |
| bool isUnscoped = false; |
| if (wrapperObject && JavascriptOperators::IsPropertyUnscopable(object, propertyId)) |
| { |
| isUnscoped = true; |
| } |
| if (propertyId != Js::Constants::NoProperty) |
| { |
| bool isConst = true; |
| if (propertyId == PropertyIds::length && Js::JavascriptArray::Is(object)) |
| { |
| // For JavascriptArrays, we allow resetting the length special property. |
| isConst = false; |
| } |
| |
| auto containsPredicate = [&](Js::DebuggerPropertyDisplayInfo* info) { return info->propId == propertyId; }; |
| if (Js::BoundFunction::Is(object) |
| && this->pMembersList->Any(containsPredicate)) |
| { |
| // Bound functions can already contain their special properties, |
| // so we need to check for that (caller and arguments). This occurs |
| // when JavascriptFunction::EntryBind() is called. Arguments can similarly |
| // already display caller in compat mode 8. |
| continue; |
| } |
| |
| AssertMsg(!this->pMembersList->Any(containsPredicate), "Special property already on the object, no need to insert."); |
| |
| InsertItem(originalObject, object, propertyId, isConst, isUnscoped, &pMethodsGroupWalker); |
| } |
| } |
| if (Js::JavascriptFunction::Is(object)) |
| { |
| // We need to special-case RegExp constructor here because it has some special properties (above) and some |
| // special enumerable properties which should all show up in the debugger. |
| JavascriptRegExpConstructor* regExp = scriptContext->GetLibrary()->GetRegExpConstructor(); |
| Js::JavascriptFunction* jsFunction = Js::JavascriptFunction::FromVar(object); |
| |
| if (regExp == object) |
| { |
| bool isUnscoped = false; |
| bool isConst = true; |
| count = regExp->GetSpecialEnumerablePropertyCount(); |
| PropertyId const * specialEnumerablePropertyIds = regExp->GetSpecialEnumerablePropertyIds(); |
| |
| for (int i = 0; i < count; i++) |
| { |
| Js::PropertyId propertyId = specialEnumerablePropertyIds[i]; |
| |
| InsertItem(originalObject, object, propertyId, isConst, isUnscoped, &pMethodsGroupWalker); |
| } |
| } |
| else if ((jsFunction->IsScriptFunction() && !jsFunction->IsJsBuiltIn()) || jsFunction->IsBoundFunction()) |
| { |
| // Adding special property length for the ScriptFunction, like it is done in JavascriptFunction::GetSpecialNonEnumerablePropertyName |
| InsertItem(originalObject, object, PropertyIds::length, true/*not editable*/, false /*isUnscoped*/, &pMethodsGroupWalker); |
| } |
| } |
| } |
| |
| // If current object has internal proto object then provide [prototype] group object. |
| if (JavascriptOperators::GetTypeId(object->GetPrototype()) != TypeIds_Null) |
| { |
| // Has [prototype] object. |
| EnsureFakeGroupObjectWalkerList(); |
| |
| RecyclableProtoObjectWalker *pProtoWalker = Anew(arena, RecyclableProtoObjectWalker, scriptContext, instance, originalInstance); |
| fakeGroupObjectWalkerList->Add(pProtoWalker); |
| } |
| } |
| |
| // If the object contains array indices. |
| if (typeId == TypeIds_Arguments) |
| { |
| // Create ArgumentsArray walker for an arguments object |
| |
| Js::ArgumentsObject * argObj = static_cast<Js::ArgumentsObject*>(instance); |
| Assert(argObj); |
| |
| if (argObj->GetNumberOfArguments() > 0 || argObj->HasNonEmptyObjectArray()) |
| { |
| innerArrayObjectWalker = Anew(arena, RecyclableArgumentsArrayWalker, scriptContext, (Var)instance, originalInstance); |
| } |
| } |
| else if (typeId == TypeIds_Map) |
| { |
| // Provide [Map] group object. |
| EnsureFakeGroupObjectWalkerList(); |
| |
| JavascriptMap* map = JavascriptMap::FromVar(object); |
| RecyclableMapObjectWalker *pMapWalker = Anew(arena, RecyclableMapObjectWalker, scriptContext, map); |
| fakeGroupObjectWalkerList->Add(pMapWalker); |
| } |
| else if (typeId == TypeIds_Set) |
| { |
| // Provide [Set] group object. |
| EnsureFakeGroupObjectWalkerList(); |
| |
| JavascriptSet* set = JavascriptSet::FromVar(object); |
| RecyclableSetObjectWalker *pSetWalker = Anew(arena, RecyclableSetObjectWalker, scriptContext, set); |
| fakeGroupObjectWalkerList->Add(pSetWalker); |
| } |
| else if (typeId == TypeIds_WeakMap) |
| { |
| // Provide [WeakMap] group object. |
| EnsureFakeGroupObjectWalkerList(); |
| |
| JavascriptWeakMap* weakMap = JavascriptWeakMap::FromVar(object); |
| RecyclableWeakMapObjectWalker *pWeakMapWalker = Anew(arena, RecyclableWeakMapObjectWalker, scriptContext, weakMap); |
| fakeGroupObjectWalkerList->Add(pWeakMapWalker); |
| } |
| else if (typeId == TypeIds_WeakSet) |
| { |
| // Provide [WeakSet] group object. |
| EnsureFakeGroupObjectWalkerList(); |
| |
| JavascriptWeakSet* weakSet = JavascriptWeakSet::FromVar(object); |
| RecyclableWeakSetObjectWalker *pWeakSetWalker = Anew(arena, RecyclableWeakSetObjectWalker, scriptContext, weakSet); |
| fakeGroupObjectWalkerList->Add(pWeakSetWalker); |
| } |
| else if (typeId == TypeIds_Promise) |
| { |
| // Provide [Promise] group object. |
| EnsureFakeGroupObjectWalkerList(); |
| |
| JavascriptPromise* promise = JavascriptPromise::FromVar(object); |
| RecyclablePromiseObjectWalker *pPromiseWalker = Anew(arena, RecyclablePromiseObjectWalker, scriptContext, promise); |
| fakeGroupObjectWalkerList->Add(pPromiseWalker); |
| } |
| else if (Js::DynamicType::Is(typeId)) |
| { |
| DynamicObject *const dynamicObject = Js::DynamicObject::FromVar(instance); |
| if (dynamicObject->HasNonEmptyObjectArray()) |
| { |
| ArrayObject* objectArray = dynamicObject->GetObjectArray(); |
| if (Js::ES5Array::Is(objectArray)) |
| { |
| innerArrayObjectWalker = Anew(arena, RecyclableES5ArrayWalker, scriptContext, objectArray, originalInstance); |
| } |
| else if (Js::JavascriptArray::Is(objectArray)) |
| { |
| innerArrayObjectWalker = Anew(arena, RecyclableArrayWalker, scriptContext, objectArray, originalInstance); |
| } |
| else |
| { |
| innerArrayObjectWalker = Anew(arena, RecyclableTypedArrayWalker, scriptContext, objectArray, originalInstance); |
| } |
| |
| innerArrayObjectWalker->SetOnlyWalkOwnProperties(true); |
| } |
| } |
| } |
| } |
| // Sort the members of the methods group |
| if (pMethodsGroupWalker) |
| { |
| pMethodsGroupWalker->Sort(); |
| } |
| |
| // Sort current pMembersList. |
| HostDebugContext* hostDebugContext = scriptContext->GetDebugContext()->GetHostDebugContext(); |
| if (hostDebugContext != nullptr) |
| { |
| hostDebugContext->SortMembersList(pMembersList, scriptContext); |
| } |
| } |
| |
| uint32 childrenCount = |
| pMembersList->Count() |
| + (innerArrayObjectWalker ? innerArrayObjectWalker->GetChildrenCount() : 0) |
| + (fakeGroupObjectWalkerList ? fakeGroupObjectWalkerList->Count() : 0); |
| |
| return childrenCount; |
| } |
| |
| void RecyclableObjectWalker::InsertItem( |
| Js::RecyclableObject *pOriginalObject, |
| Js::RecyclableObject *pObject, |
| PropertyId propertyId, |
| bool isReadOnly, |
| bool isUnscoped, |
| Js::RecyclableMethodsGroupWalker **ppMethodsGroupWalker, |
| bool shouldPinProperty /* = false*/) |
| { |
| Assert(pOriginalObject); |
| Assert(pObject); |
| Assert(propertyId); |
| Assert(ppMethodsGroupWalker); |
| |
| if (propertyId != PropertyIds::__proto__) |
| { |
| InsertItem(propertyId, isReadOnly, isUnscoped, RecyclableObjectWalker::GetObject(pOriginalObject, pObject, propertyId, scriptContext), ppMethodsGroupWalker, shouldPinProperty); |
| } |
| else // Since __proto__ defined as a Getter we should always evaluate it against object itself instead of walking prototype chain |
| { |
| InsertItem(propertyId, isReadOnly, isUnscoped, RecyclableObjectWalker::GetObject(pObject, pObject, propertyId, scriptContext), ppMethodsGroupWalker, shouldPinProperty); |
| } |
| } |
| |
| void RecyclableObjectWalker::InsertItem( |
| PropertyId propertyId, |
| bool isConst, |
| bool isUnscoped, |
| Var itemObj, |
| Js:: RecyclableMethodsGroupWalker **ppMethodsGroupWalker, |
| bool shouldPinProperty /* = false*/) |
| { |
| Assert(propertyId); |
| Assert(ppMethodsGroupWalker); |
| |
| if (itemObj == nullptr) |
| { |
| itemObj = scriptContext->GetLibrary()->GetUndefined(); |
| } |
| |
| if (shouldPinProperty) |
| { |
| const Js::PropertyRecord * propertyRecord = scriptContext->GetPropertyName(propertyId); |
| if (propertyRecord) |
| { |
| // Pin this record so that it will not go away till we are done with this break. |
| scriptContext->GetDebugContext()->GetProbeContainer()->PinPropertyRecord(propertyRecord); |
| } |
| } |
| |
| ArenaAllocator *arena = GetArenaFromContext(scriptContext); |
| |
| if (JavascriptOperators::GetTypeId(itemObj) == TypeIds_Function) |
| { |
| if (scriptContext->GetThreadContext()->GetDebugManager()->IsLocalsDisplayFlagsSet(Js::DebugManager::LocalsDisplayFlags::LocalsDisplayFlags_NoGroupMethods)) |
| { |
| DebuggerPropertyDisplayInfo *info = Anew(arena, DebuggerPropertyDisplayInfo, propertyId, itemObj, DebuggerPropertyDisplayInfoFlags_Const); |
| pMembersList->Add(info); |
| } |
| else |
| { |
| EnsureFakeGroupObjectWalkerList(); |
| |
| if (*ppMethodsGroupWalker == nullptr) |
| { |
| *ppMethodsGroupWalker = Anew(arena, RecyclableMethodsGroupWalker, scriptContext, instance); |
| fakeGroupObjectWalkerList->Add(*ppMethodsGroupWalker); |
| } |
| |
| (*ppMethodsGroupWalker)->AddItem(propertyId, itemObj); |
| } |
| } |
| else |
| { |
| DWORD flags = DebuggerPropertyDisplayInfoFlags_None; |
| flags |= isConst ? DebuggerPropertyDisplayInfoFlags_Const : 0; |
| flags |= isUnscoped ? DebuggerPropertyDisplayInfoFlags_Unscope : 0; |
| |
| DebuggerPropertyDisplayInfo *info = Anew(arena, DebuggerPropertyDisplayInfo, propertyId, itemObj, flags); |
| |
| pMembersList->Add(info); |
| } |
| } |
| |
| /*static*/ |
| Var RecyclableObjectWalker::GetObject(RecyclableObject* originalInstance, RecyclableObject* instance, PropertyId propertyId, ScriptContext* scriptContext) |
| { |
| Assert(instance); |
| Assert(!Js::IsInternalPropertyId(propertyId)); |
| |
| Var obj = nullptr; |
| try |
| { |
| if (!RecyclableObjectDisplay::GetPropertyWithScriptEnter(originalInstance, instance, propertyId, &obj, scriptContext)) |
| { |
| return instance->GetScriptContext()->GetMissingPropertyResult(); |
| } |
| } |
| catch(const JavascriptException& err) |
| { |
| Var error = err.GetAndClear()->GetThrownObject(instance->GetScriptContext()); |
| if (error != nullptr && Js::JavascriptError::Is(error)) |
| { |
| obj = error; |
| } |
| } |
| |
| return obj; |
| } |
| |
| |
| //-------------------------- |
| // RecyclableArrayAddress |
| |
| |
| RecyclableArrayAddress::RecyclableArrayAddress(Var _parentArray, unsigned int _index) |
| : parentArray(_parentArray), |
| index(_index) |
| { |
| } |
| |
| BOOL RecyclableArrayAddress::Set(Var updateObject) |
| { |
| if (Js::JavascriptArray::Is(parentArray)) |
| { |
| Js::JavascriptArray* jsArray = Js::JavascriptArray::FromVar(parentArray); |
| return jsArray->SetItem(index, updateObject, PropertyOperation_None); |
| } |
| return FALSE; |
| } |
| |
| //-------------------------- |
| // RecyclableArrayDisplay |
| |
| |
| RecyclableArrayDisplay::RecyclableArrayDisplay(ResolvedObject* resolvedObject) |
| : RecyclableObjectDisplay(resolvedObject) |
| { |
| } |
| |
| BOOL RecyclableArrayDisplay::HasChildrenInternal(Js::JavascriptArray* arrayObj) |
| { |
| Assert(arrayObj); |
| if (JavascriptOperators::GetTypeId(arrayObj->GetPrototype()) != TypeIds_Null) |
| { |
| return TRUE; |
| } |
| |
| uint32 index = arrayObj->GetNextIndex(Js::JavascriptArray::InvalidIndex); |
| return index != Js::JavascriptArray::InvalidIndex && index < arrayObj->GetLength(); |
| } |
| |
| |
| BOOL RecyclableArrayDisplay::HasChildren() |
| { |
| if (Js::JavascriptArray::Is(instance)) |
| { |
| Js::JavascriptArray* arrayObj = Js::JavascriptArray::FromVar(instance); |
| if (HasChildrenInternal(arrayObj)) |
| { |
| return TRUE; |
| } |
| } |
| return RecyclableObjectDisplay::HasChildren(); |
| } |
| |
| WeakArenaReference<IDiagObjectModelWalkerBase>* RecyclableArrayDisplay::CreateWalker() |
| { |
| return CreateAWalker<RecyclableArrayWalker>(scriptContext, instance, originalInstance); |
| } |
| |
| |
| //-------------------------- |
| // RecyclableArrayWalker |
| |
| |
| uint32 RecyclableArrayWalker::GetItemCount(Js::JavascriptArray* arrayObj) |
| { |
| if (pAbsoluteIndexList == nullptr) |
| { |
| Assert(arrayObj); |
| |
| pAbsoluteIndexList = JsUtil::List<uint32, ArenaAllocator>::New(GetArenaFromContext(scriptContext)); |
| Assert(pAbsoluteIndexList); |
| |
| uint32 dataIndex = Js::JavascriptArray::InvalidIndex; |
| uint32 descriptorIndex = Js::JavascriptArray::InvalidIndex; |
| uint32 absIndex = Js::JavascriptArray::InvalidIndex; |
| |
| do |
| { |
| if (absIndex == dataIndex) |
| { |
| dataIndex = arrayObj->GetNextIndex(dataIndex); |
| } |
| if (absIndex == descriptorIndex) |
| { |
| descriptorIndex = GetNextDescriptor(descriptorIndex); |
| } |
| |
| absIndex = min(dataIndex, descriptorIndex); |
| |
| if (absIndex == Js::JavascriptArray::InvalidIndex || absIndex >= arrayObj->GetLength()) |
| { |
| break; |
| } |
| |
| pAbsoluteIndexList->Add(absIndex); |
| |
| } while (absIndex < arrayObj->GetLength()); |
| } |
| |
| return (uint32)pAbsoluteIndexList->Count(); |
| } |
| |
| BOOL RecyclableArrayWalker::FetchItemAtIndex(Js::JavascriptArray* arrayObj, uint32 index, Var * value) |
| { |
| Assert(arrayObj); |
| Assert(value); |
| |
| return arrayObj->DirectGetItemAt(index, value); |
| } |
| |
| Var RecyclableArrayWalker::FetchItemAt(Js::JavascriptArray* arrayObj, uint32 index) |
| { |
| Assert(arrayObj); |
| return arrayObj->DirectGetItem(index); |
| } |
| |
| LPCWSTR RecyclableArrayWalker::GetIndexName(uint32 index, StringBuilder<ArenaAllocator>* stringBuilder) |
| { |
| stringBuilder->Append(_u('[')); |
| if (stringBuilder->AppendUint64(index) != 0) |
| { |
| return _u("[.]"); |
| } |
| stringBuilder->Append(_u(']')); |
| return stringBuilder->Detach(); |
| } |
| |
| RecyclableArrayWalker::RecyclableArrayWalker(ScriptContext* scriptContext, Var instance, Var originalInstance) |
| : indexedItemCount(0), |
| pAbsoluteIndexList(nullptr), |
| fOnlyOwnProperties(false), |
| RecyclableObjectWalker(scriptContext,instance,originalInstance) |
| { |
| } |
| |
| BOOL RecyclableArrayWalker::GetResolvedObject(Js::JavascriptArray* arrayObj, int index, ResolvedObject* pResolvedObject, uint32 * pabsIndex) |
| { |
| Assert(arrayObj); |
| Assert(pResolvedObject); |
| Assert(pAbsoluteIndexList); |
| Assert(pAbsoluteIndexList->Count() > index); |
| |
| // translate i'th Item to the correct array index and return |
| uint32 absIndex = pAbsoluteIndexList->Item(index); |
| pResolvedObject->obj = FetchItemAt(arrayObj, absIndex); |
| pResolvedObject->scriptContext = scriptContext; |
| pResolvedObject->typeId = JavascriptOperators::GetTypeId(pResolvedObject->obj); |
| pResolvedObject->address = nullptr; |
| |
| StringBuilder<ArenaAllocator>* builder = GetBuilder(); |
| Assert(builder); |
| builder->Reset(); |
| pResolvedObject->name = GetIndexName(absIndex, builder); |
| if (pabsIndex) |
| { |
| *pabsIndex = absIndex; |
| } |
| |
| return TRUE; |
| } |
| |
| BOOL RecyclableArrayWalker::Get(int i, ResolvedObject* pResolvedObject) |
| { |
| AssertMsg(pResolvedObject, "Bad usage of RecyclableArrayWalker::Get"); |
| |
| if (Js::JavascriptArray::Is(instance) || Js::ES5Array::Is(instance)) |
| { |
| Js::JavascriptArray* arrayObj = GetArrayObject(); |
| |
| int nonArrayElementCount = (!fOnlyOwnProperties ? RecyclableObjectWalker::GetChildrenCount() : 0); |
| |
| if (i < nonArrayElementCount) |
| { |
| return RecyclableObjectWalker::Get(i, pResolvedObject); |
| } |
| else |
| { |
| i -= nonArrayElementCount; |
| uint32 absIndex; // Absolute index |
| GetResolvedObject(arrayObj, i, pResolvedObject, &absIndex); |
| |
| pResolvedObject->address = Anew(GetArenaFromContext(scriptContext), |
| RecyclableArrayAddress, |
| instance, |
| absIndex); |
| |
| return TRUE; |
| } |
| } |
| return FALSE; |
| } |
| |
| Js::JavascriptArray* RecyclableArrayWalker::GetArrayObject() |
| { |
| Assert(Js::JavascriptArray::Is(instance) || Js::ES5Array::Is(instance)); |
| return Js::ES5Array::Is(instance) ? |
| static_cast<Js::JavascriptArray *>(RecyclableObject::FromVar(instance)) : |
| Js::JavascriptArray::FromVar(instance); |
| } |
| |
| uint32 RecyclableArrayWalker::GetChildrenCount() |
| { |
| if (Js::JavascriptArray::Is(instance) || Js::ES5Array::Is(instance)) |
| { |
| uint32 count = (!fOnlyOwnProperties ? RecyclableObjectWalker::GetChildrenCount() : 0); |
| |
| Js::JavascriptArray* arrayObj = GetArrayObject(); |
| |
| return GetItemCount(arrayObj) + count; |
| } |
| |
| return 0; |
| } |
| |
| StringBuilder<ArenaAllocator>* RecyclableArrayWalker::GetBuilder() |
| { |
| return scriptContext->GetThreadContext()->GetDebugManager()->pCurrentInterpreterLocation->stringBuilder; |
| } |
| |
| //-------------------------- |
| // RecyclableArgumentsArrayAddress |
| |
| RecyclableArgumentsArrayAddress::RecyclableArgumentsArrayAddress(Var _parentArray, unsigned int _index) |
| : parentArray(_parentArray), |
| index(_index) |
| { |
| } |
| |
| BOOL RecyclableArgumentsArrayAddress::Set(Var updateObject) |
| { |
| if (Js::ArgumentsObject::Is(parentArray)) |
| { |
| Js::ArgumentsObject* argObj = static_cast<Js::ArgumentsObject*>(parentArray); |
| return argObj->SetItem(index, updateObject, PropertyOperation_None); |
| } |
| |
| return FALSE; |
| } |
| |
| |
| //-------------------------- |
| // RecyclableArgumentsObjectDisplay |
| |
| RecyclableArgumentsObjectDisplay::RecyclableArgumentsObjectDisplay(ResolvedObject* resolvedObject, LocalsWalker *localsWalker) |
| : RecyclableObjectDisplay(resolvedObject), pLocalsWalker(localsWalker) |
| { |
| } |
| |
| BOOL RecyclableArgumentsObjectDisplay::HasChildren() |
| { |
| // It must have children otherwise object itself was not created in first place. |
| return TRUE; |
| } |
| |
| WeakArenaReference<IDiagObjectModelWalkerBase>* RecyclableArgumentsObjectDisplay::CreateWalker() |
| { |
| ReferencedArenaAdapter* pRefArena = scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena(); |
| if (pRefArena) |
| { |
| IDiagObjectModelWalkerBase* pOMWalker = Anew(pRefArena->Arena(), RecyclableArgumentsObjectWalker, scriptContext, instance, pLocalsWalker); |
| return HeapNew(WeakArenaReference<IDiagObjectModelWalkerBase>,pRefArena, pOMWalker); |
| } |
| return nullptr; |
| } |
| |
| //-------------------------- |
| // RecyclableArgumentsObjectWalker |
| |
| RecyclableArgumentsObjectWalker::RecyclableArgumentsObjectWalker(ScriptContext* pContext, Var _instance, LocalsWalker * localsWalker) |
| : RecyclableObjectWalker(pContext, _instance), pLocalsWalker(localsWalker) |
| { |
| } |
| |
| uint32 RecyclableArgumentsObjectWalker::GetChildrenCount() |
| { |
| if (innerArrayObjectWalker == nullptr) |
| { |
| uint32 count = RecyclableObjectWalker::GetChildrenCount(); |
| if (innerArrayObjectWalker != nullptr) |
| { |
| RecyclableArgumentsArrayWalker *pWalker = static_cast<RecyclableArgumentsArrayWalker *> (innerArrayObjectWalker); |
| pWalker->FetchFormalsAddress(pLocalsWalker); |
| } |
| return count; |
| } |
| |
| return RecyclableObjectWalker::GetChildrenCount(); |
| } |
| |
| |
| //-------------------------- |
| // RecyclableArgumentsArrayWalker |
| |
| RecyclableArgumentsArrayWalker::RecyclableArgumentsArrayWalker(ScriptContext* _scriptContext, Var _instance, Var _originalInstance) |
| : RecyclableArrayWalker(_scriptContext, _instance, _originalInstance), pFormalsList(nullptr) |
| { |
| } |
| |
| uint32 RecyclableArgumentsArrayWalker::GetChildrenCount() |
| { |
| if (pMembersList == nullptr) |
| { |
| Assert(Js::ArgumentsObject::Is(instance)); |
| Js::ArgumentsObject * argObj = static_cast<Js::ArgumentsObject*>(instance); |
| |
| pMembersList = JsUtil::List<DebuggerPropertyDisplayInfo *, ArenaAllocator>::New(GetArenaFromContext(scriptContext)); |
| Assert(pMembersList); |
| |
| uint32 totalCount = argObj->GetNumberOfArguments(); |
| Js::ArrayObject * objectArray = argObj->GetObjectArray(); |
| if (objectArray != nullptr && objectArray->GetLength() > totalCount) |
| { |
| totalCount = objectArray->GetLength(); |
| } |
| |
| for (uint32 index = 0; index < totalCount; index++) |
| { |
| Var itemObj; |
| if (argObj->GetItem(argObj, index, &itemObj, scriptContext)) |
| { |
| DebuggerPropertyDisplayInfo *info = Anew(GetArenaFromContext(scriptContext), DebuggerPropertyDisplayInfo, index, itemObj, DebuggerPropertyDisplayInfoFlags_None); |
| Assert(info); |
| pMembersList->Add(info); |
| } |
| } |
| } |
| |
| return pMembersList ? pMembersList->Count() : 0; |
| } |
| |
| void RecyclableArgumentsArrayWalker::FetchFormalsAddress(LocalsWalker * localsWalker) |
| { |
| Assert(localsWalker); |
| Assert(localsWalker->pFrame); |
| Js::FunctionBody *pFBody = localsWalker->pFrame->GetJavascriptFunction()->GetFunctionBody(); |
| Assert(pFBody); |
| |
| PropertyIdOnRegSlotsContainer * container = pFBody->GetPropertyIdOnRegSlotsContainer(); |
| if (container && container->propertyIdsForFormalArgs) |
| { |
| for (uint32 i = 0; i < container->propertyIdsForFormalArgs->count; i++) |
| { |
| if (container->propertyIdsForFormalArgs->elements[i] != Js::Constants::NoRegister) |
| { |
| bool isConst = false; |
| IDiagObjectAddress * address = localsWalker->FindPropertyAddress(container->propertyIdsForFormalArgs->elements[i], false, isConst); |
| if (address) |
| { |
| if (pFormalsList == nullptr) |
| { |
| pFormalsList = JsUtil::List<IDiagObjectAddress *, ArenaAllocator>::New(GetArenaFromContext(scriptContext)); |
| } |
| |
| pFormalsList->Add(address); |
| } |
| } |
| } |
| } |
| } |
| |
| BOOL RecyclableArgumentsArrayWalker::Get(int i, ResolvedObject* pResolvedObject) |
| { |
| AssertMsg(pResolvedObject, "Bad usage of RecyclableArgumentsArrayWalker::Get"); |
| |
| Assert(i >= 0); |
| Assert(Js::ArgumentsObject::Is(instance)); |
| |
| if (pMembersList && i < pMembersList->Count()) |
| { |
| Assert(pMembersList->Item(i) != nullptr); |
| |
| pResolvedObject->address = nullptr; |
| if (pFormalsList && i < pFormalsList->Count()) |
| { |
| pResolvedObject->address = pFormalsList->Item(i); |
| pResolvedObject->obj = pResolvedObject->address->GetValue(FALSE); |
| if (pResolvedObject->obj == nullptr) |
| { |
| // Temp workaround till the arguments (In jit code) work is ready. |
| Assert(Js::Configuration::Global.EnableJitInDebugMode()); |
| pResolvedObject->obj = pMembersList->Item(i)->aVar; |
| } |
| else if (pResolvedObject->obj != pMembersList->Item(i)->aVar) |
| { |
| // We set the formals value in the object itself, so that expression evaluation can reflect them correctly |
| Js::HeapArgumentsObject* argObj = static_cast<Js::HeapArgumentsObject*>(instance); |
| JavascriptOperators::SetItem(instance, argObj, (uint32)pMembersList->Item(i)->propId, pResolvedObject->obj, scriptContext, PropertyOperation_None); |
| } |
| } |
| else |
| { |
| pResolvedObject->obj = pMembersList->Item(i)->aVar; |
| } |
| Assert(pResolvedObject->obj); |
| |
| pResolvedObject->scriptContext = scriptContext; |
| pResolvedObject->typeId = JavascriptOperators::GetTypeId(pResolvedObject->obj); |
| |
| StringBuilder<ArenaAllocator>* builder = GetBuilder(); |
| Assert(builder); |
| builder->Reset(); |
| pResolvedObject->name = GetIndexName(pMembersList->Item(i)->propId, builder); |
| |
| if (pResolvedObject->typeId != TypeIds_HostDispatch && pResolvedObject->address == nullptr) |
| { |
| pResolvedObject->address = Anew(GetArenaFromContext(scriptContext), |
| RecyclableArgumentsArrayAddress, |
| instance, |
| pMembersList->Item(i)->propId); |
| } |
| return TRUE; |
| } |
| return FALSE; |
| } |
| |
| |
| |
| //-------------------------- |
| // RecyclableTypedArrayAddress |
| |
| RecyclableTypedArrayAddress::RecyclableTypedArrayAddress(Var _parentArray, unsigned int _index) |
| : RecyclableArrayAddress(_parentArray, _index) |
| { |
| } |
| |
| BOOL RecyclableTypedArrayAddress::Set(Var updateObject) |
| { |
| if (Js::TypedArrayBase::Is(parentArray)) |
| { |
| Js::TypedArrayBase* typedArrayObj = Js::TypedArrayBase::FromVar(parentArray); |
| return typedArrayObj->SetItem(index, updateObject, PropertyOperation_None); |
| } |
| |
| return FALSE; |
| } |
| |
| |
| //-------------------------- |
| // RecyclableTypedArrayDisplay |
| |
| RecyclableTypedArrayDisplay::RecyclableTypedArrayDisplay(ResolvedObject* resolvedObject) |
| : RecyclableObjectDisplay(resolvedObject) |
| { |
| } |
| |
| BOOL RecyclableTypedArrayDisplay::HasChildren() |
| { |
| if (Js::TypedArrayBase::Is(instance)) |
| { |
| Js::TypedArrayBase* typedArrayObj = Js::TypedArrayBase::FromVar(instance); |
| if (typedArrayObj->GetLength() > 0) |
| { |
| return TRUE; |
| } |
| } |
| return RecyclableObjectDisplay::HasChildren(); |
| } |
| |
| WeakArenaReference<IDiagObjectModelWalkerBase>* RecyclableTypedArrayDisplay::CreateWalker() |
| { |
| return CreateAWalker<RecyclableTypedArrayWalker>(scriptContext, instance, originalInstance); |
| } |
| |
| //-------------------------- |
| // RecyclableTypedArrayWalker |
| |
| RecyclableTypedArrayWalker::RecyclableTypedArrayWalker(ScriptContext* _scriptContext, Var _instance, Var _originalInstance) |
| : RecyclableArrayWalker(_scriptContext, _instance, _originalInstance) |
| { |
| } |
| |
| uint32 RecyclableTypedArrayWalker::GetChildrenCount() |
| { |
| if (!indexedItemCount) |
| { |
| Assert(Js::TypedArrayBase::Is(instance)); |
| |
| Js::TypedArrayBase * typedArrayObj = Js::TypedArrayBase::FromVar(instance); |
| |
| indexedItemCount = typedArrayObj->GetLength() + (!fOnlyOwnProperties ? RecyclableObjectWalker::GetChildrenCount() : 0); |
| } |
| |
| return indexedItemCount; |
| } |
| |
| BOOL RecyclableTypedArrayWalker::Get(int i, ResolvedObject* pResolvedObject) |
| { |
| AssertMsg(pResolvedObject, "Bad usage of RecyclableTypedArrayWalker::Get"); |
| |
| Assert(Js::TypedArrayBase::Is(instance)); |
| |
| Js::TypedArrayBase * typedArrayObj = Js::TypedArrayBase::FromVar(instance); |
| |
| int nonArrayElementCount = (!fOnlyOwnProperties ? RecyclableObjectWalker::GetChildrenCount() : 0); |
| |
| if (i < nonArrayElementCount) |
| { |
| return RecyclableObjectWalker::Get(i, pResolvedObject); |
| } |
| else |
| { |
| i -= nonArrayElementCount; |
| pResolvedObject->scriptContext = scriptContext; |
| pResolvedObject->obj = typedArrayObj->DirectGetItem(i); |
| pResolvedObject->typeId = JavascriptOperators::GetTypeId(pResolvedObject->obj); |
| |
| StringBuilder<ArenaAllocator>* builder = GetBuilder(); |
| Assert(builder); |
| builder->Reset(); |
| pResolvedObject->name = GetIndexName(i, builder); |
| |
| Assert(pResolvedObject->typeId != TypeIds_HostDispatch); |
| |
| pResolvedObject->address = Anew(GetArenaFromContext(scriptContext), |
| RecyclableTypedArrayAddress, |
| instance, |
| i); |
| } |
| |
| return TRUE; |
| } |
| |
| |
| //-------------------------- |
| // RecyclableES5ArrayAddress |
| |
| RecyclableES5ArrayAddress::RecyclableES5ArrayAddress(Var _parentArray, unsigned int _index) |
| : RecyclableArrayAddress(_parentArray, _index) |
| { |
| } |
| |
| BOOL RecyclableES5ArrayAddress::Set(Var updateObject) |
| { |
| if (Js::ES5Array::Is(parentArray)) |
| { |
| Js::ES5Array* arrayObj = Js::ES5Array::FromVar(parentArray); |
| return arrayObj->SetItem(index, updateObject, PropertyOperation_None); |
| } |
| |
| return FALSE; |
| } |
| |
| |
| //-------------------------- |
| // RecyclableES5ArrayDisplay |
| |
| RecyclableES5ArrayDisplay::RecyclableES5ArrayDisplay(ResolvedObject* resolvedObject) |
| : RecyclableArrayDisplay(resolvedObject) |
| { |
| } |
| |
| BOOL RecyclableES5ArrayDisplay::HasChildren() |
| { |
| if (Js::ES5Array::Is(instance)) |
| { |
| Js::JavascriptArray* arrayObj = static_cast<Js::JavascriptArray *>(RecyclableObject::FromVar(instance)); |
| if (HasChildrenInternal(arrayObj)) |
| { |
| return TRUE; |
| } |
| } |
| return RecyclableObjectDisplay::HasChildren(); |
| } |
| |
| WeakArenaReference<IDiagObjectModelWalkerBase>* RecyclableES5ArrayDisplay::CreateWalker() |
| { |
| return CreateAWalker<RecyclableES5ArrayWalker>(scriptContext, instance, originalInstance); |
| } |
| |
| //-------------------------- |
| // RecyclableES5ArrayWalker |
| |
| RecyclableES5ArrayWalker::RecyclableES5ArrayWalker(ScriptContext* _scriptContext, Var _instance, Var _originalInstance) |
| : RecyclableArrayWalker(_scriptContext, _instance, _originalInstance) |
| { |
| } |
| |
| uint32 RecyclableES5ArrayWalker::GetNextDescriptor(uint32 currentDescriptor) |
| { |
| Js::ES5Array *es5Array = static_cast<Js::ES5Array *>(RecyclableObject::FromVar(instance)); |
| IndexPropertyDescriptor* descriptor = nullptr; |
| void * descriptorValidationToken = nullptr; |
| return es5Array->GetNextDescriptor(currentDescriptor, &descriptor, &descriptorValidationToken); |
| } |
| |
| |
| BOOL RecyclableES5ArrayWalker::FetchItemAtIndex(Js::JavascriptArray* arrayObj, uint32 index, Var *value) |
| { |
| Assert(arrayObj); |
| Assert(value); |
| |
| return arrayObj->GetItem(arrayObj, index, value, scriptContext); |
| } |
| |
| Var RecyclableES5ArrayWalker::FetchItemAt(Js::JavascriptArray* arrayObj, uint32 index) |
| { |
| Assert(arrayObj); |
| Var value = nullptr; |
| if (FetchItemAtIndex(arrayObj, index, &value)) |
| { |
| return value; |
| } |
| return nullptr; |
| } |
| |
| //-------------------------- |
| // RecyclableProtoObjectWalker |
| |
| RecyclableProtoObjectWalker::RecyclableProtoObjectWalker(ScriptContext* pContext, Var instance, Var originalInstance) |
| : RecyclableObjectWalker(pContext, instance) |
| { |
| this->originalInstance = originalInstance; |
| } |
| |
| BOOL RecyclableProtoObjectWalker::GetGroupObject(ResolvedObject* pResolvedObject) |
| { |
| Assert(pResolvedObject); |
| |
| DBGPROP_ATTRIB_FLAGS defaultAttributes = DBGPROP_ATTRIB_NO_ATTRIB; |
| if (scriptContext->GetLibrary()->GetObjectPrototypeObject()->is__proto__Enabled()) |
| { |
| pResolvedObject->name = _u("__proto__"); |
| pResolvedObject->propId = PropertyIds::__proto__; |
| } |
| else |
| { |
| pResolvedObject->name = _u("[prototype]"); |
| pResolvedObject->propId = Constants::NoProperty; // This property will not be editable. |
| defaultAttributes = DBGPROP_ATTRIB_VALUE_IS_FAKE; |
| } |
| |
| RecyclableObject *obj = Js::RecyclableObject::FromVar(instance); |
| |
| Assert(obj->GetPrototype() != nullptr); |
| //withscopeObjects prototype is null |
| Assert(obj->GetPrototype()->GetTypeId() != TypeIds_Null || (obj->GetPrototype()->GetTypeId() == TypeIds_Null && obj->GetTypeId() == TypeIds_WithScopeObject)); |
| |
| pResolvedObject->obj = obj->GetPrototype(); |
| pResolvedObject->originalObj = (originalInstance != nullptr) ? Js::RecyclableObject::FromVar(originalInstance) : pResolvedObject->obj; |
| pResolvedObject->scriptContext = scriptContext; |
| pResolvedObject->typeId = JavascriptOperators::GetTypeId(pResolvedObject->obj); |
| |
| ArenaAllocator * arena = GetArenaFromContext(scriptContext); |
| pResolvedObject->objectDisplay = pResolvedObject->CreateDisplay(); |
| pResolvedObject->objectDisplay->SetDefaultTypeAttribute(defaultAttributes); |
| |
| pResolvedObject->address = Anew(arena, |
| RecyclableProtoObjectAddress, |
| instance, |
| PropertyIds::prototype, |
| pResolvedObject->obj); |
| |
| return TRUE; |
| } |
| |
| IDiagObjectAddress* RecyclableProtoObjectWalker::FindPropertyAddress(PropertyId propId, bool& isConst) |
| { |
| ResolvedObject resolvedProto; |
| GetGroupObject(&resolvedProto); |
| |
| struct AutoCleanup |
| { |
| WeakArenaReference<Js::IDiagObjectModelWalkerBase> * walkerRef; |
| IDiagObjectModelWalkerBase * walker; |
| |
| AutoCleanup() : walkerRef(nullptr), walker(nullptr) {}; |
| ~AutoCleanup() |
| { |
| if (walker) |
| { |
| walkerRef->ReleaseStrongReference(); |
| } |
| if (walkerRef) |
| { |
| HeapDelete(walkerRef); |
| } |
| } |
| } autoCleanup; |
| Assert(resolvedProto.objectDisplay); |
| autoCleanup.walkerRef = resolvedProto.objectDisplay->CreateWalker(); |
| autoCleanup.walker = autoCleanup.walkerRef->GetStrongReference(); |
| return autoCleanup.walker ? autoCleanup.walker->FindPropertyAddress(propId, isConst) : nullptr; |
| } |
| |
| //-------------------------- |
| // RecyclableProtoObjectAddress |
| |
| RecyclableProtoObjectAddress::RecyclableProtoObjectAddress(Var _parentObj, Js::PropertyId _propId, Js::Var _value) |
| : RecyclableObjectAddress(_parentObj, _propId, _value, false /*isInDeadZone*/) |
| { |
| } |
| |
| //-------------------------- |
| // RecyclableCollectionObjectWalker |
| template <typename TData> const char16* RecyclableCollectionObjectWalker<TData>::Name() { static_assert(false, _u("Must use specialization")); } |
| template <> const char16* RecyclableCollectionObjectWalker<JavascriptMap>::Name() { return _u("[Map]"); } |
| template <> const char16* RecyclableCollectionObjectWalker<JavascriptSet>::Name() { return _u("[Set]"); } |
| template <> const char16* RecyclableCollectionObjectWalker<JavascriptWeakMap>::Name() { return _u("[WeakMap]"); } |
| template <> const char16* RecyclableCollectionObjectWalker<JavascriptWeakSet>::Name() { return _u("[WeakSet]"); } |
| |
| template <typename TData> |
| BOOL RecyclableCollectionObjectWalker<TData>::GetGroupObject(ResolvedObject* pResolvedObject) |
| { |
| pResolvedObject->name = Name(); |
| pResolvedObject->propId = Constants::NoProperty; |
| pResolvedObject->obj = instance; |
| pResolvedObject->scriptContext = scriptContext; |
| pResolvedObject->typeId = JavascriptOperators::GetTypeId(pResolvedObject->obj); |
| pResolvedObject->address = nullptr; |
| |
| typedef RecyclableCollectionObjectDisplay<TData> RecyclableDataObjectDisplay; |
| pResolvedObject->objectDisplay = Anew(GetArenaFromContext(scriptContext), RecyclableDataObjectDisplay, scriptContext, pResolvedObject->name, this); |
| |
| return TRUE; |
| } |
| |
| template <typename TData> |
| BOOL RecyclableCollectionObjectWalker<TData>::Get(int i, ResolvedObject* pResolvedObject) |
| { |
| auto builder = scriptContext->GetThreadContext()->GetDebugManager()->pCurrentInterpreterLocation->stringBuilder; |
| builder->Reset(); |
| builder->AppendUint64(i); |
| pResolvedObject->name = builder->Detach(); |
| pResolvedObject->propId = Constants::NoProperty; |
| pResolvedObject->obj = instance; |
| pResolvedObject->scriptContext = scriptContext; |
| pResolvedObject->typeId = JavascriptOperators::GetTypeId(pResolvedObject->obj); |
| pResolvedObject->address = nullptr; |
| |
| pResolvedObject->objectDisplay = CreateTDataDisplay(pResolvedObject, i); |
| |
| return TRUE; |
| } |
| |
| template <typename TData> |
| IDiagObjectModelDisplay* RecyclableCollectionObjectWalker<TData>::CreateTDataDisplay(ResolvedObject* resolvedObject, int i) |
| { |
| Var key = propertyList->Item(i).key; |
| Var value = propertyList->Item(i).value; |
| return Anew(GetArenaFromContext(scriptContext), RecyclableKeyValueDisplay, resolvedObject->scriptContext, key, value, resolvedObject->name); |
| } |
| |
| template <> |
| IDiagObjectModelDisplay* RecyclableCollectionObjectWalker<JavascriptSet>::CreateTDataDisplay(ResolvedObject* resolvedObject, int i) |
| { |
| resolvedObject->obj = propertyList->Item(i).value; |
| IDiagObjectModelDisplay* display = resolvedObject->CreateDisplay(); |
| display->SetDefaultTypeAttribute(DBGPROP_ATTRIB_VALUE_READONLY | DBGPROP_ATTRIB_VALUE_IS_FAKE); |
| return display; |
| } |
| |
| template <> |
| IDiagObjectModelDisplay* RecyclableCollectionObjectWalker<JavascriptWeakSet>::CreateTDataDisplay(ResolvedObject* resolvedObject, int i) |
| { |
| resolvedObject->obj = propertyList->Item(i).value; |
| IDiagObjectModelDisplay* display = resolvedObject->CreateDisplay(); |
| display->SetDefaultTypeAttribute(DBGPROP_ATTRIB_VALUE_READONLY | DBGPROP_ATTRIB_VALUE_IS_FAKE); |
| return display; |
| } |
| |
| template <typename TData> |
| uint32 RecyclableCollectionObjectWalker<TData>::GetChildrenCount() |
| { |
| TData* data = TData::FromVar(instance); |
| if (data->Size() > 0 && propertyList == nullptr) |
| { |
| propertyList = JsUtil::List<RecyclableCollectionObjectWalkerPropertyData<TData>, ArenaAllocator>::New(GetArenaFromContext(scriptContext)); |
| GetChildren(); |
| } |
| |
| return data->Size(); |
| } |
| |
| template <> |
| void RecyclableCollectionObjectWalker<JavascriptMap>::GetChildren() |
| { |
| JavascriptMap* data = JavascriptMap::FromVar(instance); |
| auto iterator = data->GetIterator(); |
| while (iterator.Next()) |
| { |
| Var key = iterator.Current().Key(); |
| Var value = iterator.Current().Value(); |
| propertyList->Add(RecyclableCollectionObjectWalkerPropertyData<JavascriptMap>(key, value)); |
| } |
| } |
| |
| template <> |
| void RecyclableCollectionObjectWalker<JavascriptSet>::GetChildren() |
| { |
| JavascriptSet* data = JavascriptSet::FromVar(instance); |
| auto iterator = data->GetIterator(); |
| while (iterator.Next()) |
| { |
| Var value = iterator.Current(); |
| propertyList->Add(RecyclableCollectionObjectWalkerPropertyData<JavascriptSet>(value)); |
| } |
| } |
| |
| template <> |
| void RecyclableCollectionObjectWalker<JavascriptWeakMap>::GetChildren() |
| { |
| JavascriptWeakMap* data = JavascriptWeakMap::FromVar(instance); |
| data->Map([&](Var key, Var value) |
| { |
| propertyList->Add(RecyclableCollectionObjectWalkerPropertyData<JavascriptWeakMap>(key, value)); |
| }); |
| } |
| |
| template <> |
| void RecyclableCollectionObjectWalker<JavascriptWeakSet>::GetChildren() |
| { |
| JavascriptWeakSet* data = JavascriptWeakSet::FromVar(instance); |
| data->Map([&](Var value) |
| { |
| propertyList->Add(RecyclableCollectionObjectWalkerPropertyData<JavascriptWeakSet>(value)); |
| }); |
| } |
| |
| //-------------------------- |
| // RecyclableCollectionObjectDisplay |
| template <typename TData> |
| LPCWSTR RecyclableCollectionObjectDisplay<TData>::Value(int radix) |
| { |
| StringBuilder<ArenaAllocator>* builder = scriptContext->GetThreadContext()->GetDebugManager()->pCurrentInterpreterLocation->stringBuilder; |
| builder->Reset(); |
| |
| builder->AppendCppLiteral(_u("size = ")); |
| builder->AppendUint64(walker->GetChildrenCount()); |
| |
| return builder->Detach(); |
| } |
| |
| template <typename TData> |
| WeakArenaReference<IDiagObjectModelWalkerBase>* RecyclableCollectionObjectDisplay<TData>::CreateWalker() |
| { |
| if (walker) |
| { |
| ReferencedArenaAdapter* pRefArena = scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena(); |
| if (pRefArena) |
| { |
| return HeapNew(WeakArenaReference<IDiagObjectModelWalkerBase>, pRefArena, walker); |
| } |
| } |
| return nullptr; |
| } |
| |
| //-------------------------- |
| // RecyclableKeyValueDisplay |
| WeakArenaReference<IDiagObjectModelWalkerBase>* RecyclableKeyValueDisplay::CreateWalker() |
| { |
| ReferencedArenaAdapter* pRefArena = scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena(); |
| if (pRefArena) |
| { |
| IDiagObjectModelWalkerBase* pOMWalker = Anew(pRefArena->Arena(), RecyclableKeyValueWalker, scriptContext, key, value); |
| return HeapNew(WeakArenaReference<IDiagObjectModelWalkerBase>, pRefArena, pOMWalker); |
| } |
| return nullptr; |
| } |
| |
| LPCWSTR RecyclableKeyValueDisplay::Value(int radix) |
| { |
| ResolvedObject ro; |
| ro.scriptContext = scriptContext; |
| |
| ro.obj = key; |
| RecyclableObjectDisplay keyDisplay(&ro); |
| |
| ro.obj = value; |
| RecyclableObjectDisplay valueDisplay(&ro); |
| |
| // Note, RecyclableObjectDisplay::Value(int) uses the shared string builder |
| // so we cannot call it while building our string below. Call both before hand. |
| const char16* keyValue = keyDisplay.Value(radix); |
| const char16* valueValue = valueDisplay.Value(radix); |
| |
| StringBuilder<ArenaAllocator>* builder = scriptContext->GetThreadContext()->GetDebugManager()->pCurrentInterpreterLocation->stringBuilder; |
| builder->Reset(); |
| |
| builder->Append('['); |
| builder->AppendSz(keyValue); |
| builder->AppendCppLiteral(_u(", ")); |
| builder->AppendSz(valueValue); |
| builder->Append(']'); |
| |
| return builder->Detach(); |
| } |
| |
| //-------------------------- |
| // RecyclableKeyValueWalker |
| BOOL RecyclableKeyValueWalker::Get(int i, ResolvedObject* pResolvedObject) |
| { |
| if (i == 0) |
| { |
| pResolvedObject->name = _u("key"); |
| pResolvedObject->obj = key; |
| } |
| else if (i == 1) |
| { |
| pResolvedObject->name = _u("value"); |
| pResolvedObject->obj = value; |
| } |
| else |
| { |
| Assert(false); |
| return FALSE; |
| } |
| |
| pResolvedObject->propId = Constants::NoProperty; |
| pResolvedObject->scriptContext = scriptContext; |
| pResolvedObject->typeId = JavascriptOperators::GetTypeId(pResolvedObject->obj); |
| pResolvedObject->objectDisplay = pResolvedObject->CreateDisplay(); |
| pResolvedObject->objectDisplay->SetDefaultTypeAttribute(DBGPROP_ATTRIB_VALUE_READONLY | DBGPROP_ATTRIB_VALUE_IS_FAKE); |
| pResolvedObject->address = nullptr; |
| |
| return TRUE; |
| } |
| |
| //-------------------------- |
| // RecyclableProxyObjectDisplay |
| |
| RecyclableProxyObjectDisplay::RecyclableProxyObjectDisplay(ResolvedObject* resolvedObject) |
| : RecyclableObjectDisplay(resolvedObject) |
| { |
| } |
| |
| WeakArenaReference<IDiagObjectModelWalkerBase>* RecyclableProxyObjectDisplay::CreateWalker() |
| { |
| ReferencedArenaAdapter* pRefArena = scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena(); |
| if (pRefArena) |
| { |
| IDiagObjectModelWalkerBase* pOMWalker = Anew(pRefArena->Arena(), RecyclableProxyObjectWalker, scriptContext, instance); |
| return HeapNew(WeakArenaReference<IDiagObjectModelWalkerBase>, pRefArena, pOMWalker); |
| } |
| return nullptr; |
| } |
| |
| //-------------------------- |
| // RecyclableProxyObjectWalker |
| |
| RecyclableProxyObjectWalker::RecyclableProxyObjectWalker(ScriptContext* pContext, Var _instance) |
| : RecyclableObjectWalker(pContext, _instance) |
| { |
| } |
| |
| BOOL RecyclableProxyObjectWalker::GetGroupObject(ResolvedObject* pResolvedObject) |
| { |
| pResolvedObject->name = _u("[Proxy]"); |
| pResolvedObject->propId = Constants::NoProperty; |
| pResolvedObject->obj = instance; |
| pResolvedObject->scriptContext = scriptContext; |
| pResolvedObject->typeId = JavascriptOperators::GetTypeId(pResolvedObject->obj); |
| pResolvedObject->address = nullptr; |
| |
| pResolvedObject->objectDisplay = Anew(GetArenaFromContext(scriptContext), RecyclableProxyObjectDisplay, pResolvedObject); |
| pResolvedObject->objectDisplay->SetDefaultTypeAttribute(DBGPROP_ATTRIB_VALUE_READONLY | DBGPROP_ATTRIB_VALUE_IS_FAKE); |
| return TRUE; |
| } |
| |
| BOOL RecyclableProxyObjectWalker::Get(int i, ResolvedObject* pResolvedObject) |
| { |
| JavascriptProxy* proxy = JavascriptProxy::FromVar(instance); |
| if (proxy->GetTarget() == nullptr || proxy->GetHandler() == nullptr) |
| { |
| return FALSE; |
| } |
| if (i == 0) |
| { |
| pResolvedObject->name = _u("[target]"); |
| pResolvedObject->obj = proxy->GetTarget(); |
| } |
| else if (i == 1) |
| { |
| pResolvedObject->name = _u("[handler]"); |
| pResolvedObject->obj = proxy->GetHandler(); |
| } |
| else |
| { |
| Assert(false); |
| return FALSE; |
| } |
| |
| pResolvedObject->propId = Constants::NoProperty; |
| pResolvedObject->scriptContext = scriptContext; |
| pResolvedObject->typeId = JavascriptOperators::GetTypeId(pResolvedObject->obj); |
| pResolvedObject->objectDisplay = pResolvedObject->CreateDisplay(); |
| pResolvedObject->objectDisplay->SetDefaultTypeAttribute(DBGPROP_ATTRIB_VALUE_READONLY | DBGPROP_ATTRIB_VALUE_IS_FAKE); |
| pResolvedObject->address = Anew(GetArenaFromContext(pResolvedObject->scriptContext), |
| RecyclableObjectAddress, |
| pResolvedObject->scriptContext->GetGlobalObject(), |
| Js::PropertyIds::Proxy, |
| pResolvedObject->obj, |
| false /*isInDeadZone*/); |
| |
| return TRUE; |
| } |
| |
| //-------------------------- |
| // RecyclablePromiseObjectDisplay |
| |
| RecyclablePromiseObjectDisplay::RecyclablePromiseObjectDisplay(ResolvedObject* resolvedObject) |
| : RecyclableObjectDisplay(resolvedObject) |
| { |
| } |
| |
| WeakArenaReference<IDiagObjectModelWalkerBase>* RecyclablePromiseObjectDisplay::CreateWalker() |
| { |
| ReferencedArenaAdapter* pRefArena = scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena(); |
| if (pRefArena) |
| { |
| IDiagObjectModelWalkerBase* pOMWalker = Anew(pRefArena->Arena(), RecyclablePromiseObjectWalker, scriptContext, instance); |
| return HeapNew(WeakArenaReference<IDiagObjectModelWalkerBase>, pRefArena, pOMWalker); |
| } |
| return nullptr; |
| } |
| |
| //-------------------------- |
| // RecyclablePromiseObjectWalker |
| |
| RecyclablePromiseObjectWalker::RecyclablePromiseObjectWalker(ScriptContext* pContext, Var _instance) |
| : RecyclableObjectWalker(pContext, _instance) |
| { |
| } |
| |
| BOOL RecyclablePromiseObjectWalker::GetGroupObject(ResolvedObject* pResolvedObject) |
| { |
| pResolvedObject->name = _u("[Promise]"); |
| pResolvedObject->propId = Constants::NoProperty; |
| pResolvedObject->obj = instance; |
| pResolvedObject->scriptContext = scriptContext; |
| pResolvedObject->typeId = JavascriptOperators::GetTypeId(pResolvedObject->obj); |
| pResolvedObject->address = nullptr; |
| |
| pResolvedObject->objectDisplay = Anew(GetArenaFromContext(scriptContext), RecyclablePromiseObjectDisplay, pResolvedObject); |
| pResolvedObject->objectDisplay->SetDefaultTypeAttribute(DBGPROP_ATTRIB_VALUE_READONLY | DBGPROP_ATTRIB_VALUE_IS_FAKE); |
| return TRUE; |
| } |
| |
| BOOL RecyclablePromiseObjectWalker::Get(int i, ResolvedObject* pResolvedObject) |
| { |
| JavascriptPromise* promise = JavascriptPromise::FromVar(instance); |
| |
| if (i == 0) |
| { |
| pResolvedObject->name = _u("[status]"); |
| |
| switch (promise->GetStatus()) |
| { |
| case JavascriptPromise::PromiseStatusCode_Undefined: |
| pResolvedObject->obj = scriptContext->GetLibrary()->GetUndefinedDisplayString(); |
| break; |
| case JavascriptPromise::PromiseStatusCode_Unresolved: |
| pResolvedObject->obj = scriptContext->GetLibrary()->CreateStringFromCppLiteral(_u("pending")); |
| case JavascriptPromise::PromiseStatusCode_HasResolution: |
| pResolvedObject->obj = scriptContext->GetLibrary()->CreateStringFromCppLiteral(_u("resolved")); |
| break; |
| case JavascriptPromise::PromiseStatusCode_HasRejection: |
| pResolvedObject->obj = scriptContext->GetLibrary()->CreateStringFromCppLiteral(_u("rejected")); |
| break; |
| default: |
| AssertMsg(false, "New PromiseStatusCode not handled in debugger"); |
| pResolvedObject->obj = scriptContext->GetLibrary()->GetUndefinedDisplayString(); |
| break; |
| } |
| } |
| else if (i == 1) |
| { |
| pResolvedObject->name = _u("[value]"); |
| Var result = promise->GetResult(); |
| pResolvedObject->obj = result != nullptr ? result : scriptContext->GetLibrary()->GetUndefinedDisplayString(); |
| } |
| else |
| { |
| Assert(false); |
| return FALSE; |
| } |
| |
| pResolvedObject->propId = Constants::NoProperty; |
| pResolvedObject->scriptContext = scriptContext; |
| pResolvedObject->typeId = JavascriptOperators::GetTypeId(pResolvedObject->obj); |
| pResolvedObject->objectDisplay = pResolvedObject->CreateDisplay(); |
| pResolvedObject->objectDisplay->SetDefaultTypeAttribute(DBGPROP_ATTRIB_VALUE_READONLY | DBGPROP_ATTRIB_VALUE_IS_FAKE); |
| pResolvedObject->address = nullptr; |
| |
| return TRUE; |
| } |
| |
| // --------------------------- |
| // RecyclableMethodsGroupWalker |
| RecyclableMethodsGroupWalker::RecyclableMethodsGroupWalker(ScriptContext* scriptContext, Var instance) |
| : RecyclableObjectWalker(scriptContext,instance) |
| { |
| } |
| |
| void RecyclableMethodsGroupWalker::AddItem(Js::PropertyId propertyId, Var obj) |
| { |
| if (pMembersList == nullptr) |
| { |
| pMembersList = JsUtil::List<DebuggerPropertyDisplayInfo *, ArenaAllocator>::New(GetArenaFromContext(scriptContext)); |
| } |
| |
| Assert(pMembersList); |
| |
| DebuggerPropertyDisplayInfo *info = Anew(GetArenaFromContext(scriptContext), DebuggerPropertyDisplayInfo, propertyId, obj, DebuggerPropertyDisplayInfoFlags_Const); |
| Assert(info); |
| pMembersList->Add(info); |
| } |
| |
| uint32 RecyclableMethodsGroupWalker::GetChildrenCount() |
| { |
| return pMembersList ? pMembersList->Count() : 0; |
| } |
| |
| BOOL RecyclableMethodsGroupWalker::Get(int i, ResolvedObject* pResolvedObject) |
| { |
| AssertMsg(pResolvedObject, "Bad usage of RecyclableMethodsGroupWalker::Get"); |
| |
| return RecyclableObjectWalker::Get(i, pResolvedObject); |
| } |
| |
| BOOL RecyclableMethodsGroupWalker::GetGroupObject(ResolvedObject* pResolvedObject) |
| { |
| Assert(pResolvedObject); |
| |
| // This is fake [Methods] object. |
| pResolvedObject->name = _u("[Methods]"); |
| pResolvedObject->obj = Js::RecyclableObject::FromVar(instance); |
| pResolvedObject->scriptContext = scriptContext; |
| pResolvedObject->typeId = JavascriptOperators::GetTypeId(pResolvedObject->obj); |
| pResolvedObject->address = nullptr; // Methods object will not be editable |
| |
| pResolvedObject->objectDisplay = Anew(GetArenaFromContext(scriptContext), RecyclableMethodsGroupDisplay, this, pResolvedObject); |
| |
| return TRUE; |
| } |
| |
| void RecyclableMethodsGroupWalker::Sort() |
| { |
| HostDebugContext* hostDebugContext = this->scriptContext->GetDebugContext()->GetHostDebugContext(); |
| if (hostDebugContext != nullptr) |
| { |
| hostDebugContext->SortMembersList(pMembersList, scriptContext); |
| } |
| } |
| |
| RecyclableMethodsGroupDisplay::RecyclableMethodsGroupDisplay(RecyclableMethodsGroupWalker *_methodGroupWalker, ResolvedObject* resolvedObject) |
| : methodGroupWalker(_methodGroupWalker), |
| RecyclableObjectDisplay(resolvedObject) |
| { |
| } |
| |
| LPCWSTR RecyclableMethodsGroupDisplay::Type() |
| { |
| return _u(""); |
| } |
| |
| LPCWSTR RecyclableMethodsGroupDisplay::Value(int radix) |
| { |
| return _u("{...}"); |
| } |
| |
| BOOL RecyclableMethodsGroupDisplay::HasChildren() |
| { |
| return methodGroupWalker ? TRUE : FALSE; |
| } |
| |
| DBGPROP_ATTRIB_FLAGS RecyclableMethodsGroupDisplay::GetTypeAttribute() |
| { |
| return DBGPROP_ATTRIB_VALUE_READONLY | DBGPROP_ATTRIB_VALUE_IS_FAKE | DBGPROP_ATTRIB_VALUE_IS_METHOD | DBGPROP_ATTRIB_VALUE_IS_EXPANDABLE; |
| } |
| |
| WeakArenaReference<IDiagObjectModelWalkerBase>* RecyclableMethodsGroupDisplay::CreateWalker() |
| { |
| if (methodGroupWalker) |
| { |
| ReferencedArenaAdapter* pRefArena = scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena(); |
| if (pRefArena) |
| { |
| return HeapNew(WeakArenaReference<IDiagObjectModelWalkerBase>, pRefArena, methodGroupWalker); |
| } |
| } |
| return nullptr; |
| } |
| |
| |
| ScopeVariablesGroupDisplay::ScopeVariablesGroupDisplay(VariableWalkerBase *walker, ResolvedObject* resolvedObject) |
| : scopeGroupWalker(walker), |
| RecyclableObjectDisplay(resolvedObject) |
| { |
| } |
| |
| LPCWSTR ScopeVariablesGroupDisplay::Type() |
| { |
| return _u(""); |
| } |
| |
| LPCWSTR ScopeVariablesGroupDisplay::Value(int radix) |
| { |
| if (ActivationObject::Is(instance)) |
| { |
| // The scope is defined by the activation object. |
| Js::RecyclableObject *object = Js::RecyclableObject::FromVar(instance); |
| try |
| { |
| // Trying to find out the JavascriptFunction from the scope. |
| Var value = nullptr; |
| if (object->GetTypeId() == TypeIds_ActivationObject && GetPropertyWithScriptEnter(object, object, PropertyIds::arguments, &value, scriptContext)) |
| { |
| if (Js::RecyclableObject::Is(value)) |
| { |
| Js::RecyclableObject *argObject = Js::RecyclableObject::FromVar(value); |
| Var calleeFunc = nullptr; |
| if (GetPropertyWithScriptEnter(argObject, argObject, PropertyIds::callee, &calleeFunc, scriptContext) && Js::JavascriptFunction::Is(calleeFunc)) |
| { |
| Js::JavascriptFunction *calleeFunction = Js::JavascriptFunction::FromVar(calleeFunc); |
| Js::FunctionBody *pFuncBody = calleeFunction->GetFunctionBody(); |
| |
| if (pFuncBody) |
| { |
| const char16* pDisplayName = pFuncBody->GetDisplayName(); |
| if (pDisplayName) |
| { |
| StringBuilder<ArenaAllocator>* builder = GetStringBuilder(); |
| builder->Reset(); |
| builder->AppendSz(pDisplayName); |
| return builder->Detach(); |
| } |
| } |
| } |
| } |
| } |
| } |
| catch(const JavascriptException& err) |
| { |
| err.GetAndClear(); // discard exception object |
| } |
| |
| return _u(""); |
| } |
| else |
| { |
| // The scope is defined by a slot array object so grab the function body out to get the function name. |
| ScopeSlots slotArray = ScopeSlots(reinterpret_cast<Var*>(instance)); |
| |
| if(slotArray.IsFunctionScopeSlotArray()) |
| { |
| Js::FunctionBody *functionBody = slotArray.GetFunctionInfo()->GetFunctionBody(); |
| return functionBody->GetDisplayName(); |
| } |
| else |
| { |
| // handling for block/catch scope |
| return _u(""); |
| } |
| } |
| } |
| |
| BOOL ScopeVariablesGroupDisplay::HasChildren() |
| { |
| return scopeGroupWalker ? TRUE : FALSE; |
| } |
| |
| DBGPROP_ATTRIB_FLAGS ScopeVariablesGroupDisplay::GetTypeAttribute() |
| { |
| return DBGPROP_ATTRIB_VALUE_READONLY | DBGPROP_ATTRIB_VALUE_IS_FAKE | DBGPROP_ATTRIB_VALUE_IS_EXPANDABLE; |
| } |
| |
| WeakArenaReference<IDiagObjectModelWalkerBase>* ScopeVariablesGroupDisplay::CreateWalker() |
| { |
| if (scopeGroupWalker) |
| { |
| ReferencedArenaAdapter* pRefArena = scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena(); |
| if (pRefArena) |
| { |
| return HeapNew(WeakArenaReference<IDiagObjectModelWalkerBase>, pRefArena, scopeGroupWalker); |
| } |
| } |
| return nullptr; |
| } |
| |
| GlobalsScopeVariablesGroupDisplay::GlobalsScopeVariablesGroupDisplay(VariableWalkerBase *walker, ResolvedObject* resolvedObject) |
| : globalsGroupWalker(walker), |
| RecyclableObjectDisplay(resolvedObject) |
| { |
| } |
| |
| LPCWSTR GlobalsScopeVariablesGroupDisplay::Type() |
| { |
| return _u(""); |
| } |
| |
| LPCWSTR GlobalsScopeVariablesGroupDisplay::Value(int radix) |
| { |
| return _u(""); |
| } |
| |
| BOOL GlobalsScopeVariablesGroupDisplay::HasChildren() |
| { |
| return globalsGroupWalker ? globalsGroupWalker->GetChildrenCount() > 0 : FALSE; |
| } |
| |
| DBGPROP_ATTRIB_FLAGS GlobalsScopeVariablesGroupDisplay::GetTypeAttribute() |
| { |
| return DBGPROP_ATTRIB_VALUE_READONLY | DBGPROP_ATTRIB_VALUE_IS_FAKE | (HasChildren() ? DBGPROP_ATTRIB_VALUE_IS_EXPANDABLE : 0); |
| } |
| |
| WeakArenaReference<IDiagObjectModelWalkerBase>* GlobalsScopeVariablesGroupDisplay::CreateWalker() |
| { |
| if (globalsGroupWalker) |
| { |
| ReferencedArenaAdapter* pRefArena = scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena(); |
| if (pRefArena) |
| { |
| return HeapNew(WeakArenaReference<IDiagObjectModelWalkerBase>, pRefArena, globalsGroupWalker); |
| } |
| } |
| return nullptr; |
| } |
| #ifdef ENABLE_MUTATION_BREAKPOINT |
| PendingMutationBreakpointDisplay::PendingMutationBreakpointDisplay(ResolvedObject* resolvedObject, MutationType _mutationType) |
| : RecyclableObjectDisplay(resolvedObject), mutationType(_mutationType) |
| { |
| AssertMsg(_mutationType > MutationTypeNone && _mutationType < MutationTypeAll, "Invalid mutationType value passed to PendingMutationBreakpointDisplay"); |
| } |
| |
| WeakArenaReference<IDiagObjectModelWalkerBase>* PendingMutationBreakpointDisplay::CreateWalker() |
| { |
| ReferencedArenaAdapter* pRefArena = scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena(); |
| if (pRefArena) |
| { |
| IDiagObjectModelWalkerBase* pOMWalker = Anew(pRefArena->Arena(), PendingMutationBreakpointWalker, scriptContext, instance, this->mutationType); |
| return HeapNew(WeakArenaReference<IDiagObjectModelWalkerBase>, pRefArena, pOMWalker); |
| } |
| return nullptr; |
| } |
| |
| uint32 PendingMutationBreakpointWalker::GetChildrenCount() |
| { |
| switch (this->mutationType) |
| { |
| case MutationTypeUpdate: |
| return 3; |
| case MutationTypeDelete: |
| case MutationTypeAdd: |
| return 2; |
| default: |
| AssertMsg(false, "Invalid mutationType"); |
| return 0; |
| } |
| } |
| |
| PendingMutationBreakpointWalker::PendingMutationBreakpointWalker(ScriptContext* pContext, Var _instance, MutationType mutationType) |
| : RecyclableObjectWalker(pContext, _instance) |
| { |
| this->mutationType = mutationType; |
| } |
| |
| BOOL PendingMutationBreakpointWalker::Get(int i, ResolvedObject* pResolvedObject) |
| { |
| Js::MutationBreakpoint *mutationBreakpoint = scriptContext->GetDebugContext()->GetProbeContainer()->GetDebugManager()->GetActiveMutationBreakpoint(); |
| Assert(mutationBreakpoint); |
| if (mutationBreakpoint != nullptr) |
| { |
| if (i == 0) |
| { |
| // <Property Name> [Adding] : New Value |
| // <Property Name> [Changing] : Old Value |
| // <Property Name> [Deleting] : Old Value |
| WCHAR * displayName = AnewArray(GetArenaFromContext(scriptContext), WCHAR, PENDING_MUTATION_VALUE_MAX_NAME); |
| swprintf_s(displayName, PENDING_MUTATION_VALUE_MAX_NAME, _u("%s [%s]"), mutationBreakpoint->GetBreakPropertyName(), Js::MutationBreakpoint::GetBreakMutationTypeName(mutationType)); |
| pResolvedObject->name = displayName; |
| if (mutationType == MutationTypeUpdate || mutationType == MutationTypeDelete) |
| { |
| // Old/Current value |
| PropertyId breakPId = mutationBreakpoint->GetBreakPropertyId(); |
| pResolvedObject->propId = breakPId; |
| pResolvedObject->obj = JavascriptOperators::OP_GetProperty(mutationBreakpoint->GetMutationObjectVar(), breakPId, scriptContext); |
| } |
| else |
| { |
| // New Value |
| pResolvedObject->obj = mutationBreakpoint->GetBreakNewValueVar(); |
| pResolvedObject->propId = Constants::NoProperty; |
| } |
| } |
| else if ((i == 1) && (mutationType == MutationTypeUpdate)) |
| { |
| pResolvedObject->name = _u("[New Value]"); |
| pResolvedObject->obj = mutationBreakpoint->GetBreakNewValueVar(); |
| pResolvedObject->propId = Constants::NoProperty; |
| } |
| else if (((i == 1) && (mutationType != MutationTypeUpdate)) || (i == 2)) |
| { |
| WCHAR * displayName = AnewArray(GetArenaFromContext(scriptContext), WCHAR, PENDING_MUTATION_VALUE_MAX_NAME); |
| swprintf_s(displayName, PENDING_MUTATION_VALUE_MAX_NAME, _u("[Property container %s]"), mutationBreakpoint->GetParentPropertyName()); |
| pResolvedObject->name = displayName; |
| pResolvedObject->obj = mutationBreakpoint->GetMutationObjectVar(); |
| pResolvedObject->propId = mutationBreakpoint->GetParentPropertyId(); |
| } |
| else |
| { |
| Assert(false); |
| return FALSE; |
| } |
| |
| pResolvedObject->scriptContext = scriptContext; |
| pResolvedObject->typeId = JavascriptOperators::GetTypeId(pResolvedObject->obj); |
| pResolvedObject->objectDisplay = pResolvedObject->CreateDisplay(); |
| pResolvedObject->objectDisplay->SetDefaultTypeAttribute(DBGPROP_ATTRIB_VALUE_READONLY | DBGPROP_ATTRIB_VALUE_IS_FAKE); |
| pResolvedObject->address = nullptr; // TODO: (SaAgarwa) Currently Pending mutation values are not editable, will do as part of another WI |
| |
| return TRUE; |
| } |
| return FALSE; |
| } |
| #endif |
| |
| //-------------------------- |
| // RecyclableSimdObjectWalker |
| |
| template <typename simdType, uint elementCount> |
| BOOL RecyclableSimdObjectWalker<simdType, elementCount>::Get(int i, ResolvedObject* pResolvedObject) |
| { |
| Assert(elementCount == 4 || elementCount == 8 || elementCount == 16); // SIMD types such as int32x4, int8x16, int16x8 |
| Assert(i >= 0 && i <= elementCount); |
| |
| simdType* simd = simdType::FromVar(instance); |
| SIMDValue value = simd->GetValue(); |
| |
| WCHAR* indexName = AnewArray(GetArenaFromContext(scriptContext), WCHAR, SIMD_INDEX_VALUE_MAX); |
| Assert(indexName); |
| swprintf_s(indexName, SIMD_INDEX_VALUE_MAX, _u("[%d]"), i); |
| pResolvedObject->name = indexName; |
| |
| TypeId simdTypeId = JavascriptOperators::GetTypeId(instance); |
| |
| switch (simdTypeId) |
| { |
| case TypeIds_SIMDInt32x4: |
| pResolvedObject->obj = JavascriptNumber::ToVarWithCheck(value.i32[i], scriptContext); |
| break; |
| case TypeIds_SIMDFloat32x4: |
| pResolvedObject->obj = JavascriptNumber::ToVarWithCheck(value.f32[i], scriptContext); |
| break; |
| case TypeIds_SIMDInt8x16: |
| pResolvedObject->obj = JavascriptNumber::ToVarWithCheck(value.i8[i], scriptContext); |
| break; |
| case TypeIds_SIMDInt16x8: |
| pResolvedObject->obj = JavascriptNumber::ToVarWithCheck(value.i16[i], scriptContext); |
| break; |
| case TypeIds_SIMDBool32x4: |
| pResolvedObject->obj = JavascriptBoolean::ToVar(value.i32[i], scriptContext); |
| break; |
| case TypeIds_SIMDBool8x16: |
| pResolvedObject->obj = JavascriptBoolean::ToVar(value.i8[i], scriptContext); |
| break; |
| case TypeIds_SIMDBool16x8: |
| pResolvedObject->obj = JavascriptBoolean::ToVar(value.i16[i], scriptContext); |
| break; |
| case TypeIds_SIMDUint32x4: |
| pResolvedObject->obj = JavascriptNumber::ToVarWithCheck(value.u32[i], scriptContext); |
| break; |
| case TypeIds_SIMDUint8x16: |
| pResolvedObject->obj = JavascriptNumber::ToVarWithCheck(value.u8[i], scriptContext); |
| break; |
| case TypeIds_SIMDUint16x8: |
| pResolvedObject->obj = JavascriptNumber::ToVarWithCheck(value.u16[i], scriptContext); |
| break; |
| default: |
| AssertMsg(false, "Unexpected SIMD typeId"); |
| return FALSE; |
| } |
| |
| pResolvedObject->propId = Constants::NoProperty; |
| pResolvedObject->scriptContext = scriptContext; |
| pResolvedObject->typeId = simdTypeId; |
| pResolvedObject->objectDisplay = pResolvedObject->CreateDisplay(); |
| pResolvedObject->objectDisplay->SetDefaultTypeAttribute(DBGPROP_ATTRIB_VALUE_READONLY | DBGPROP_ATTRIB_VALUE_IS_FAKE); |
| pResolvedObject->address = nullptr; |
| |
| return TRUE; |
| } |
| |
| //-------------------------- |
| // RecyclableSimdObjectDisplay |
| |
| template <typename simdType, typename simdWalker> |
| LPCWSTR RecyclableSimdObjectDisplay<simdType, simdWalker>::Type() |
| { |
| TypeId simdTypeId = JavascriptOperators::GetTypeId(instance); |
| |
| switch (simdTypeId) |
| { |
| case TypeIds_SIMDInt32x4: |
| return _u("SIMD.Int32x4"); |
| case TypeIds_SIMDFloat32x4: |
| return _u("SIMD.Float32x4"); |
| case TypeIds_SIMDInt8x16: |
| return _u("SIMD.Int8x16"); |
| case TypeIds_SIMDInt16x8: |
| return _u("SIMD.Int16x8"); |
| case TypeIds_SIMDBool32x4: |
| return _u("SIMD.Bool32x4"); |
| case TypeIds_SIMDBool8x16: |
| return _u("SIMD.Bool8x16"); |
| case TypeIds_SIMDBool16x8: |
| return _u("SIMD.Bool16x8"); |
| case TypeIds_SIMDUint32x4: |
| return _u("SIMD.Uint32x4"); |
| case TypeIds_SIMDUint8x16: |
| return _u("SIMD.Uint8x16"); |
| case TypeIds_SIMDUint16x8: |
| return _u("SIMD.Uint16x8"); |
| default: |
| AssertMsg(false, "Unexpected SIMD typeId"); |
| return nullptr; |
| } |
| } |
| |
| template <typename simdType, typename simdWalker> |
| LPCWSTR RecyclableSimdObjectDisplay<simdType, simdWalker>::Value(int radix) |
| { |
| StringBuilder<ArenaAllocator>* builder = GetStringBuilder(); |
| builder->Reset(); |
| |
| simdType* simd = simdType::FromVar(instance); |
| SIMDValue value = simd->GetValue(); |
| |
| char16* stringBuffer = AnewArray(GetArenaFromContext(scriptContext), char16, SIMD_STRING_BUFFER_MAX); |
| |
| simdType::ToStringBuffer(value, stringBuffer, SIMD_STRING_BUFFER_MAX, scriptContext); |
| |
| builder->AppendSz(stringBuffer); |
| |
| return builder->Detach(); |
| } |
| |
| template <typename simdType, typename simdWalker> |
| WeakArenaReference<IDiagObjectModelWalkerBase>* RecyclableSimdObjectDisplay<simdType, simdWalker>::CreateWalker() |
| { |
| ReferencedArenaAdapter* pRefArena = scriptContext->GetThreadContext()->GetDebugManager()->GetDiagnosticArena(); |
| if (pRefArena) |
| { |
| IDiagObjectModelWalkerBase* pOMWalker = Anew(pRefArena->Arena(), simdWalker, scriptContext, instance); |
| return HeapNew(WeakArenaReference<IDiagObjectModelWalkerBase>, pRefArena, pOMWalker); |
| } |
| return nullptr; |
| } |
| } |