Skip to content

Commit

Permalink
Fix debugging script blocks that aren't in files
Browse files Browse the repository at this point in the history
The code relied on running `list 1 <MaxInt>` but that wasn't being run
in the debug context because we allow-list commands to prevent pollution
of the history, and missed it. Like the `prompt` and interactive
commands (which `list` could be but is not when we run it) we need to
check for this exact `list` command and run it under the debugger.

Moreover, we also weren't locking the `debugInfoHandle`, nor were we
correctly checking if `scriptListingLines` was empty (it was never
null), and our shortcut to skip allocation was broken. Actually we can't
skip allocation, but we can at least skip superfluous conversions.
  • Loading branch information
andyleejordan committed Sep 12, 2023
1 parent 9e19755 commit 0fcb641
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 13 deletions.
21 changes: 16 additions & 5 deletions src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -930,13 +930,24 @@ internal async void OnDebuggerStopAsync(object sender, DebuggerStopEventArgs e)
if (_remoteFileManager is not null && string.IsNullOrEmpty(localScriptPath))
{
// Get the current script listing and create the buffer
PSCommand command = new PSCommand().AddScript($"list 1 {int.MaxValue}");
IReadOnlyList<PSObject> scriptListingLines;
await debugInfoHandle.WaitAsync().ConfigureAwait(false);
try
{
// This command must be run through `ExecuteInDebugger`!
PSCommand psCommand = new PSCommand().AddScript($"list 1 {int.MaxValue}");

IReadOnlyList<PSObject> scriptListingLines =
await _executionService.ExecutePSCommandAsync<PSObject>(
command, CancellationToken.None).ConfigureAwait(false);
scriptListingLines =
await _executionService.ExecutePSCommandAsync<PSObject>(
psCommand,
CancellationToken.None).ConfigureAwait(false);
}
finally
{
debugInfoHandle.Release();
}

if (scriptListingLines is not null)
if (scriptListingLines.Count > 0)
{
int linePrefixLength = 0;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,12 @@ public override IReadOnlyList<TResult> Run(CancellationToken cancellationToken)
// state that we sync with the LSP debugger. The former commands we want to send
// through PowerShell's `Debugger.ProcessCommand` so that they work as expected, but
// the latter we must not send through it else they pollute the history as this
// PowerShell API does not let us exclude them from it.
// PowerShell API does not let us exclude them from it. Notably we also need to send
// the `prompt` command and our special `list 1 <MaxInt>` through the debugger too.
// The former needs the context in order to show `DBG 1>` etc., and the latter is
// used to gather the lines when debugging a script that isn't in a file.
return _pwsh.Runspace.Debugger.InBreakpoint
&& (PowerShellExecutionOptions.AddToHistory || IsPromptCommand(_psCommand) || _pwsh.Runspace.RunspaceIsRemote)
&& (PowerShellExecutionOptions.AddToHistory || IsPromptOrListCommand(_psCommand) || _pwsh.Runspace.RunspaceIsRemote)
? ExecuteInDebugger(cancellationToken)
: ExecuteNormally(cancellationToken);
}
Expand All @@ -87,7 +90,7 @@ public override IReadOnlyList<TResult> Run(CancellationToken cancellationToken)

public override string ToString() => _psCommand.GetInvocationText();

private static bool IsPromptCommand(PSCommand command)
private static bool IsPromptOrListCommand(PSCommand command)
{
if (command.Commands.Count is not 1
|| command.Commands[0] is { IsScript: false } or { Parameters.Count: > 0 })
Expand All @@ -96,7 +99,8 @@ private static bool IsPromptCommand(PSCommand command)
}

string commandText = command.Commands[0].CommandText;
return commandText.Equals("prompt", StringComparison.OrdinalIgnoreCase);
return commandText.Equals("prompt", StringComparison.OrdinalIgnoreCase)
|| commandText.Equals($"list 1 {int.MaxValue}", StringComparison.OrdinalIgnoreCase);
}

private IReadOnlyList<TResult> ExecuteNormally(CancellationToken cancellationToken)
Expand Down Expand Up @@ -301,11 +305,10 @@ private IReadOnlyList<TResult> ExecuteInDebugger(CancellationToken cancellationT
return Array.Empty<TResult>();
}

// If we've been asked for a PSObject, no need to allocate a new collection
if (typeof(TResult) == typeof(PSObject)
&& outputCollection is IReadOnlyList<TResult> resultCollection)
// If we've been asked for a PSObject, no need to convert
if (typeof(TResult) == typeof(PSObject))
{
return resultCollection;
return new List<PSObject>(outputCollection) as IReadOnlyList<TResult>;
}

// Otherwise, convert things over
Expand Down

0 comments on commit 0fcb641

Please sign in to comment.