Skip to content

Commit f2e6fef

Browse files
committed
Debugger support for split param/body scope case during default.
For the split scope we were still using the older way of getting information from the function body when we walk the slot array walker. However that is not true as the propertyidslotarraycontainer was meant for slots for body's variable and also the regslotcontainer will not have right information as well. Fixed that by The variable which are copied from param scope to body scope (due to split scope) will be put regslotcontainer if needed. While walking we need to distinguish that the scope is split and we need to make use of DebuggerScope. Also the paramscope could be activation object so we need to have DiagParamScopeInObject to distinguish while locals walking. Linear scan gave the problem as for the split case the formals are duplicated as variables. Our intention was to restore only formals, so I fixed that by limiting the loop till the formalsCount only.
1 parent b5b94d9 commit f2e6fef

6 files changed

Lines changed: 136 additions & 63 deletions

File tree

lib/Backend/LinearScan.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1626,7 +1626,9 @@ LinearScan::FillBailOutRecord(IR::Instr * instr)
16261626

16271627
if (hasFormalArgs)
16281628
{
1629-
for (uint32 index = functionBody->GetFirstNonTempLocalIndex(); index < functionBody->GetEndNonTempLocalIndex(); index++)
1629+
Assert(functionBody->GetInParamsCount() > 0);
1630+
uint32 endIndex = min(functionBody->GetFirstNonTempLocalIndex() + functionBody->GetInParamsCount() - 1, functionBody->GetEndNonTempLocalIndex());
1631+
for (uint32 index = functionBody->GetFirstNonTempLocalIndex(); index < endIndex; index++)
16301632
{
16311633
StackSym * stackSym = this->func->m_symTable->FindStackSym(index);
16321634
if (stackSym != nullptr)

lib/Runtime/Base/FunctionBody.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5318,6 +5318,8 @@ namespace Js
53185318
return _u("DiagWithScope");
53195319
case DiagExtraScopesType::DiagParamScope:
53205320
return _u("DiagParamScope");
5321+
case DiagExtraScopesType::DiagParamScopeInObject:
5322+
return _u("DiagParamScopeInObject");
53215323
default:
53225324
AssertMsg(false, "Missing a debug scope type.");
53235325
return _u("");
@@ -5493,6 +5495,12 @@ namespace Js
54935495
|| this->scopeType == Js::DiagCatchScopeInSlot;
54945496
}
54955497

5498+
bool DebuggerScope::IsParamScope() const
5499+
{
5500+
return this->scopeType == Js::DiagParamScope
5501+
|| this->scopeType == Js::DiagParamScopeInObject;
5502+
}
5503+
54965504
// Gets whether or not the scope has any properties in it.
54975505
bool DebuggerScope::HasProperties() const
54985506
{
@@ -5681,7 +5689,7 @@ namespace Js
56815689
{
56825690
Js::DebuggerScope *debuggerScope = pScopeChain->Item(i);
56835691
DebuggerScopeProperty debuggerScopeProperty;
5684-
if (debuggerScope->scopeType != DiagParamScope && debuggerScope->TryGetProperty(propertyId, location, &debuggerScopeProperty))
5692+
if (!debuggerScope->IsParamScope() && debuggerScope->TryGetProperty(propertyId, location, &debuggerScopeProperty))
56855693
{
56865694
bool isOffsetInScope = debuggerScope->IsOffsetInScope(offset);
56875695

lib/Runtime/Base/FunctionBody.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ namespace Js
6767
DiagBlockScopeInObject, // Block scope in activation object
6868
DiagBlockScopeRangeEnd, // Used to end a block scope range.
6969
DiagParamScope, // The scope represents symbols at formals
70+
DiagParamScopeInObject, // The scope represents symbols at formals and formal scope in activation object
7071
};
7172

7273
class PropertyGuard
@@ -3475,6 +3476,7 @@ namespace Js
34753476
bool IsCatchScope() const;
34763477
bool IsWithScope() const;
34773478
bool IsSlotScope() const;
3479+
bool IsParamScope() const;
34783480
bool HasProperties() const;
34793481
bool IsAncestorOf(const DebuggerScope* potentialChildScope);
34803482
bool AreAllPropertiesInDeadZone(int byteCodeOffset) const;

lib/Runtime/ByteCode/ByteCodeEmitter.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2773,7 +2773,7 @@ void ByteCodeGenerator::PopulateFormalsScope(uint beginOffset, FuncInfo *funcInf
27732773
{
27742774
if (debuggerScope == nullptr)
27752775
{
2776-
debuggerScope = RecordStartScopeObject(pnode, Js::DiagParamScope);
2776+
debuggerScope = RecordStartScopeObject(pnode, funcInfo->paramScope && funcInfo->paramScope->GetIsObject() ? Js::DiagParamScopeInObject : Js::DiagParamScope);
27772777
debuggerScope->SetBegin(beginOffset);
27782778
}
27792779

@@ -3274,7 +3274,7 @@ void ByteCodeGenerator::EmitOneFunction(ParseNode *pnode)
32743274
{
32753275
// Emit bytecode to copy the initial values from param names to their corresponding body bindings.
32763276
// We have to do this after the rest param is marked as false for need declaration.
3277-
paramScope->ForEachSymbol([this, funcInfo, paramScope](Symbol* param) {
3277+
paramScope->ForEachSymbol([this, funcInfo, paramScope, byteCodeFunction](Symbol* param) {
32783278
Symbol* varSym = funcInfo->GetBodyScope()->FindLocalSymbol(param->GetName());
32793279
Assert(varSym || param->GetIsArguments());
32803280
Assert(param->GetIsArguments() || param->IsInSlot(funcInfo));
@@ -3289,6 +3289,12 @@ void ByteCodeGenerator::EmitOneFunction(ParseNode *pnode)
32893289
slot = slot + (paramScope->GetIsObject() ? 0 : Js::ScopeSlots::FirstSlotIndex);
32903290

32913291
this->m_writer.SlotI1(op, tempReg, slot, profileId);
3292+
3293+
if (ShouldTrackDebuggerMetadata() && !varSym->GetIsArguments() && !varSym->IsInSlot(funcInfo))
3294+
{
3295+
byteCodeFunction->InsertSymbolToRegSlotList(varSym->GetName(), varSym->GetLocation(), funcInfo->varRegsCount);
3296+
}
3297+
32923298
this->EmitPropStore(tempReg, varSym, varSym->GetPid(), funcInfo);
32933299
funcInfo->ReleaseTmpRegister(tempReg);
32943300
}

lib/Runtime/Debug/DiagObjectModel.cpp

Lines changed: 112 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -546,53 +546,16 @@ namespace Js
546546
return !*isPropertyInDebuggerScope;
547547
}
548548

549-
// Gets an adjusted offset for the current bytecode location based on which stack frame we're in.
550-
// If we're in the top frame (leaf node), then the byte code offset should remain as is, to reflect
551-
// the current position of the instruction pointer. If we're not in the top frame, we need to subtract
552-
// 1 as the byte code location will be placed at the next statement to be executed at the top frame.
553-
// In the case of block scoping, this is an inaccurate location for viewing variables since the next
554-
// statement could be beyond the current block scope. For inspection, we want to remain in the
555-
// current block that the function was called from.
556-
// An example is this:
557-
// function foo() { ... } // Frame 0 (with breakpoint inside)
558-
// function bar() { // Frame 1
559-
// {
560-
// let a = 0;
561-
// foo(); // <-- Inspecting here, foo is already evaluated.
562-
// }
563-
// foo(); // <-- Byte code offset is now here, so we need to -1 to get back in the block scope.
564549
int VariableWalkerBase::GetAdjustedByteCodeOffset() const
565550
{
566-
Assert(pFrame);
567-
int offset = pFrame->GetByteCodeOffset();
568-
if (!pFrame->IsTopFrame() && pFrame->IsInterpreterFrame())
569-
{
570-
// Native frames are already adjusted so just need to adjust interpreted
571-
// frames that are not the top frame.
572-
--offset;
573-
}
574-
575-
return offset;
551+
return LocalsWalker::GetAdjustedByteCodeOffset(pFrame);
576552
}
577553

578554
DebuggerScope * VariableWalkerBase::GetScopeWhenHaltAtFormals()
579555
{
580556
if (IsWalkerForCurrentFrame())
581557
{
582-
Js::ScopeObjectChain * scopeObjectChain = pFrame->GetJavascriptFunction()->GetFunctionBody()->GetScopeObjectChain();
583-
584-
if (scopeObjectChain != nullptr && scopeObjectChain->pScopeChain != nullptr)
585-
{
586-
int currentOffset = GetAdjustedByteCodeOffset();
587-
for (int i = 0; i < scopeObjectChain->pScopeChain->Count(); i++)
588-
{
589-
Js::DebuggerScope * scope = scopeObjectChain->pScopeChain->Item(i);
590-
if (scope->scopeType == Js::DiagParamScope && scope->GetEnd() > currentOffset)
591-
{
592-
return scope;
593-
}
594-
}
595-
}
558+
return LocalsWalker::GetScopeWhenHaltAtFormals(pFrame);
596559
}
597560

598561
return nullptr;
@@ -631,13 +594,35 @@ namespace Js
631594

632595
if (slotArray.IsFunctionScopeSlotArray())
633596
{
597+
DebuggerScope *formalScope = GetScopeWhenHaltAtFormals();
634598
Js::FunctionBody *pFBody = slotArray.GetFunctionBody();
635-
if (pFBody->GetPropertyIdsForScopeSlotArray() != nullptr)
599+
uint slotArrayCount = slotArray.GetCount();
600+
601+
if (formalScope != nullptr && !pFBody->IsParamAndBodyScopeMerged())
636602
{
637-
uint slotArrayCount = slotArray.GetCount();
603+
Assert(pFBody->paramScopeSlotArraySize > 0);
638604
pMembersList = JsUtil::List<DebuggerPropertyDisplayInfo *, ArenaAllocator>::New(arena, slotArrayCount);
639605

640-
DebuggerScope *formalScope = GetScopeWhenHaltAtFormals();
606+
for (ulong i = 0; i < slotArrayCount; i++)
607+
{
608+
Js::DebuggerScopeProperty scopeProperty = formalScope->scopeProperties->Item(i);
609+
610+
Var value = slotArray.Get(i);
611+
bool isInDeadZone = pFrame->GetScriptContext()->IsUndeclBlockVar(value);
612+
613+
DebuggerPropertyDisplayInfo *pair = AllocateNewPropertyDisplayInfo(
614+
scopeProperty.propId,
615+
value,
616+
false/*isConst*/,
617+
isInDeadZone);
618+
619+
Assert(pair != nullptr);
620+
pMembersList->Add(pair);
621+
}
622+
}
623+
else if (pFBody->GetPropertyIdsForScopeSlotArray() != nullptr)
624+
{
625+
pMembersList = JsUtil::List<DebuggerPropertyDisplayInfo *, ArenaAllocator>::New(arena, slotArrayCount);
641626

642627
for (ulong i = 0; i < slotArrayCount; i++)
643628
{
@@ -970,7 +955,7 @@ namespace Js
970955
Js::DebuggerScope *debuggerScope = pScopeObjectChain->pScopeChain->Item(i);
971956
bool isScopeInRange = debuggerScope->IsOffsetInScope(bytecodeOffset);
972957
if (isScopeInRange
973-
&& debuggerScope->scopeType != DiagParamScope
958+
&& !debuggerScope->IsParamScope()
974959
&& (debuggerScope->IsOwnScope() || (debuggerScope->scopeType == DiagBlockScopeDirect && debuggerScope->HasProperties())))
975960
{
976961
switch (debuggerScope->scopeType)
@@ -1107,27 +1092,46 @@ namespace Js
11071092
pVarWalkers->Add(Anew(arena, RootObjectVariablesWalker, pFrame, pFrame->GetRootObject(), UIGroupType_None));
11081093
}
11091094

1110-
DWORD localsType = GetCurrentFramesLocalsType(pFrame);
1111-
if (localsType & FramesLocalType::LocalType_Reg)
1112-
{
1113-
pVarWalkers->Add(Anew(arena, RegSlotVariablesWalker, pFrame, nullptr /*not debugger scope*/, UIGroupType_None, !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowSuperReference)));
1114-
}
1115-
if (localsType & FramesLocalType::LocalType_InObject)
1116-
{
1117-
Assert(scopeCount > 0);
1118-
pVarWalker = Anew(arena, ObjectVariablesWalker, pFrame, pDisplay->GetItem(nextStartIndex++), UIGroupType_None, !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowLexicalThis), !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowSuperReference));
1119-
}
1120-
else if (localsType & FramesLocalType::LocalType_InSlot)
1095+
DebuggerScope *formalScope = GetScopeWhenHaltAtFormals(pFrame);
1096+
1097+
// If we are halted at formal place, and param and body scopes are splitted we need to make use of formal debugger scope to walk those variables.
1098+
if (!pFBody->IsParamAndBodyScopeMerged() && formalScope != nullptr)
11211099
{
11221100
Assert(scopeCount > 0);
1123-
pVarWalker = Anew(arena, SlotArrayVariablesWalker, pFrame, (Js::Var *)pDisplay->GetItem(nextStartIndex++), UIGroupType_None, !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowLexicalThis), !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowSuperReference));
1101+
if (formalScope->scopeType == Js::DiagParamScopeInObject)
1102+
{
1103+
pVarWalker = Anew(arena, ObjectVariablesWalker, pFrame, (Js::Var *)pDisplay->GetItem(nextStartIndex++), UIGroupType_None, !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowLexicalThis), !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowSuperReference));
1104+
}
1105+
else
1106+
{
1107+
Assert(pFBody->paramScopeSlotArraySize > 0);
1108+
pVarWalker = Anew(arena, SlotArrayVariablesWalker, pFrame, (Js::Var *)pDisplay->GetItem(nextStartIndex++), UIGroupType_None, !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowLexicalThis), !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowSuperReference));
1109+
}
11241110
}
1125-
else if (scopeCount > 0 && pFBody->GetFrameDisplayRegister() != 0)
1111+
else
11261112
{
1127-
Assert((Var)pDisplay->GetItem(0) == pFrame->GetScriptContext()->GetLibrary()->GetNull());
1113+
DWORD localsType = GetCurrentFramesLocalsType(pFrame);
1114+
if (localsType & FramesLocalType::LocalType_Reg)
1115+
{
1116+
pVarWalkers->Add(Anew(arena, RegSlotVariablesWalker, pFrame, nullptr /*not debugger scope*/, UIGroupType_None, !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowSuperReference)));
1117+
}
1118+
if (localsType & FramesLocalType::LocalType_InObject)
1119+
{
1120+
Assert(scopeCount > 0);
1121+
pVarWalker = Anew(arena, ObjectVariablesWalker, pFrame, pDisplay->GetItem(nextStartIndex++), UIGroupType_None, !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowLexicalThis), !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowSuperReference));
1122+
}
1123+
else if (localsType & FramesLocalType::LocalType_InSlot)
1124+
{
1125+
Assert(scopeCount > 0);
1126+
pVarWalker = Anew(arena, SlotArrayVariablesWalker, pFrame, (Js::Var *)pDisplay->GetItem(nextStartIndex++), UIGroupType_None, !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowLexicalThis), !!(frameWalkerFlags & FrameWalkerFlags::FW_AllowSuperReference));
1127+
}
1128+
else if (scopeCount > 0 && pFBody->GetFrameDisplayRegister() != 0 )
1129+
{
1130+
Assert((Var)pDisplay->GetItem(0) == pFrame->GetScriptContext()->GetLibrary()->GetNull() || !pFBody->IsParamAndBodyScopeMerged());
11281131

1129-
// A dummy scope with nullptr register is created. Skip this.
1130-
nextStartIndex++;
1132+
// A dummy scope with nullptr register is created. Skip this.
1133+
nextStartIndex++;
1134+
}
11311135
}
11321136

11331137
if (pVarWalker)
@@ -1366,6 +1370,55 @@ namespace Js
13661370
return totalLocalsCount;
13671371
}
13681372

1373+
/*static*/
1374+
DebuggerScope * LocalsWalker::GetScopeWhenHaltAtFormals(DiagStackFrame* frame)
1375+
{
1376+
Js::ScopeObjectChain * scopeObjectChain = frame->GetJavascriptFunction()->GetFunctionBody()->GetScopeObjectChain();
1377+
1378+
if (scopeObjectChain != nullptr && scopeObjectChain->pScopeChain != nullptr)
1379+
{
1380+
int currentOffset = GetAdjustedByteCodeOffset(frame);
1381+
for (int i = 0; i < scopeObjectChain->pScopeChain->Count(); i++)
1382+
{
1383+
Js::DebuggerScope * scope = scopeObjectChain->pScopeChain->Item(i);
1384+
if (scope->IsParamScope() && scope->GetEnd() > currentOffset)
1385+
{
1386+
return scope;
1387+
}
1388+
}
1389+
}
1390+
1391+
return nullptr;
1392+
}
1393+
1394+
// Gets an adjusted offset for the current bytecode location based on which stack frame we're in.
1395+
// If we're in the top frame (leaf node), then the byte code offset should remain as is, to reflect
1396+
// the current position of the instruction pointer. If we're not in the top frame, we need to subtract
1397+
// 1 as the byte code location will be placed at the next statement to be executed at the top frame.
1398+
// In the case of block scoping, this is an inaccurate location for viewing variables since the next
1399+
// statement could be beyond the current block scope. For inspection, we want to remain in the
1400+
// current block that the function was called from.
1401+
// An example is this:
1402+
// function foo() { ... } // Frame 0 (with breakpoint inside)
1403+
// function bar() { // Frame 1
1404+
// {
1405+
// let a = 0;
1406+
// foo(); // <-- Inspecting here, foo is already evaluated.
1407+
// }
1408+
// foo(); // <-- Byte code offset is now here, so we need to -1 to get back in the block scope.
1409+
int LocalsWalker::GetAdjustedByteCodeOffset(DiagStackFrame* frame)
1410+
{
1411+
int offset = frame->GetByteCodeOffset();
1412+
if (!frame->IsTopFrame() && frame->IsInterpreterFrame())
1413+
{
1414+
// Native frames are already adjusted so just need to adjust interpreted
1415+
// frames that are not the top frame.
1416+
--offset;
1417+
}
1418+
1419+
return offset;
1420+
}
1421+
13691422
/*static*/
13701423
DWORD LocalsWalker::GetCurrentFramesLocalsType(DiagStackFrame* frame)
13711424
{

lib/Runtime/Debug/DiagObjectModel.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,8 @@ namespace Js
316316
virtual BOOL GetGroupObject(ResolvedObject* pResolvedObject) {return FALSE; }
317317

318318
static DWORD GetCurrentFramesLocalsType(DiagStackFrame* frame);
319+
static DebuggerScope * GetScopeWhenHaltAtFormals(DiagStackFrame* frame);
320+
static int GetAdjustedByteCodeOffset(DiagStackFrame* frame);
319321

320322
IDiagObjectAddress * FindPropertyAddress(PropertyId propId, bool& isConst) override;
321323

0 commit comments

Comments
 (0)