Skip to content

Commit 26f978c

Browse files
committed
Document and improve FetchStackFramesAsync
1 parent 92818c8 commit 26f978c

File tree

1 file changed

+26
-20
lines changed

1 file changed

+26
-20
lines changed

src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -668,6 +668,11 @@ private async Task<VariableContainerDetails> FetchVariableContainerAsync(string
668668
}
669669
catch (CmdletInvocationException ex)
670670
{
671+
// It's possible to be asked to run `Get-Variable -Scope N` where N is a number that
672+
// exceeds the available scopes. In this case, the command throws this exception,
673+
// but there's nothing we can do about it, nor can we know the number of scopes that
674+
// exist, and we shouldn't crash the debugger, so we just return no results instead.
675+
// All other exceptions should be thrown again.
671676
if (!ex.ErrorRecord.CategoryInfo.Reason.Equals("PSArgumentOutOfRangeException"))
672677
{
673678
throw;
@@ -757,6 +762,8 @@ private bool AddToAutoVariables(PSObject psvariable, string scope)
757762
if (((variableScope & constantAllScope) == constantAllScope)
758763
|| ((variableScope & readonlyAllScope) == readonlyAllScope))
759764
{
765+
// The constructor we are using here does not automatically add the dollar prefix,
766+
// so we do it manually.
760767
string prefixedVariableName = VariableDetails.DollarPrefix + variableName;
761768
if (globalScopeVariables.Children.ContainsKey(prefixedVariableName))
762769
{
@@ -769,30 +776,27 @@ private bool AddToAutoVariables(PSObject psvariable, string scope)
769776

770777
private async Task FetchStackFramesAsync(string scriptNameOverride)
771778
{
772-
PSCommand psCommand = new PSCommand();
773-
// The serialization depth to retrieve variables from remote runspaces.
774-
const int serializationDepth = 3;
775-
776779
// This glorious hack ensures that Get-PSCallStack returns a list of CallStackFrame
777780
// objects (or "deserialized" CallStackFrames) when attached to a runspace in another
778-
// process. Without the intermediate variable Get-PSCallStack inexplicably returns
779-
// an array of strings containing the formatted output of the CallStackFrame list.
780-
string callStackVarName = $"$global:{PsesGlobalVariableNamePrefix}CallStack";
781-
782-
string getPSCallStack = $"Get-PSCallStack | ForEach-Object {{ [void]{callStackVarName}.add(@($PSItem,$PSItem.GetFrameVariables())) }}";
783-
784-
// If we're attached to a remote runspace, we need to serialize the callstack prior to transport
785-
// because the default depth is too shallow
781+
// process. Without the intermediate variable Get-PSCallStack inexplicably returns an
782+
// array of strings containing the formatted output of the CallStackFrame list. So we
783+
// run a script that builds the list of CallStackFrames and their variables.
784+
const string callStackVarName = $"$global:{PsesGlobalVariableNamePrefix}CallStack";
785+
const string getPSCallStack = $"Get-PSCallStack | ForEach-Object {{ [void]{callStackVarName}.Add(@($PSItem, $PSItem.GetFrameVariables())) }}";
786+
787+
// If we're attached to a remote runspace, we need to serialize the list prior to
788+
// transport because the default depth is too shallow. From testing, we determined the
789+
// correct depth is 3. The script always calls `Get-PSCallStack`. On a local machine, we
790+
// just return its results. On a remote machine we serialize it first and then later
791+
// deserialize it.
786792
bool isOnRemoteMachine = _psesHost.CurrentRunspace.IsOnRemoteMachine;
787793
string returnSerializedIfOnRemoteMachine = isOnRemoteMachine
788-
? $"[Management.Automation.PSSerializer]::Serialize({callStackVarName}, {serializationDepth})"
794+
? $"[Management.Automation.PSSerializer]::Serialize({callStackVarName}, 3)"
789795
: callStackVarName;
790796

791-
// We have to deal with a shallow serialization depth with ExecutePSCommandAsync as well, hence the serializer to get full var information
792-
psCommand.AddScript($"[Collections.ArrayList]{callStackVarName} = @(); {getPSCallStack}; {returnSerializedIfOnRemoteMachine}");
793-
794-
795-
// PSObject is used here instead of the specific type because we get deserialized objects from remote sessions and want a common interface
797+
// PSObject is used here instead of the specific type because we get deserialized
798+
// objects from remote sessions and want a common interface.
799+
var psCommand = new PSCommand().AddScript($"[Collections.ArrayList]{callStackVarName} = @(); {getPSCallStack}; {returnSerializedIfOnRemoteMachine}");
796800
IReadOnlyList<PSObject> results = await _executionService.ExecutePSCommandAsync<PSObject>(psCommand, CancellationToken.None).ConfigureAwait(false);
797801

798802
IEnumerable callStack = isOnRemoteMachine
@@ -816,11 +820,13 @@ private async Task FetchStackFramesAsync(string scriptNameOverride)
816820

817821
foreach (DictionaryEntry entry in callStackVariables)
818822
{
819-
// TODO: This should be deduplicated into a new function for the other variable handling as well
823+
// TODO: This should be deduplicated into a new function.
820824
object psVarValue = isOnRemoteMachine
821825
? (entry.Value as PSObject).Properties["Value"].Value
822826
: (entry.Value as PSVariable).Value;
823-
// The constructor we are using here does not automatically add the dollar prefix
827+
828+
// The constructor we are using here does not automatically add the dollar
829+
// prefix, so we do it manually.
824830
string psVarName = VariableDetails.DollarPrefix + entry.Key.ToString();
825831
var variableDetails = new VariableDetails(psVarName, psVarValue) { Id = nextVariableId++ };
826832
variables.Add(variableDetails);

0 commit comments

Comments
 (0)