Skip to content

Commit

Permalink
Add artifical stack frame to represent context
Browse files Browse the repository at this point in the history
When stepping into certain contexts like a param block's default value
expression, the engine does not provide a call stack frame to represent
it. In this scenario we want to create an artifical call stack frame to
represent the context we've stepped into.
  • Loading branch information
SeeminglyScience committed Aug 16, 2022
1 parent c25d0d2 commit eb9069d
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 7 deletions.
43 changes: 39 additions & 4 deletions src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -994,10 +994,45 @@ await _remoteFileManager.FetchRemoteFileAsync(
// Augment the top stack frame with details from the stop event
if (invocationTypeScriptPositionProperty.GetValue(e.InvocationInfo) is IScriptExtent scriptExtent)
{
stackFrameDetails[0].StartLineNumber = scriptExtent.StartLineNumber;
stackFrameDetails[0].EndLineNumber = scriptExtent.EndLineNumber;
stackFrameDetails[0].StartColumnNumber = scriptExtent.StartColumnNumber;
stackFrameDetails[0].EndColumnNumber = scriptExtent.EndColumnNumber;
StackFrameDetails targetFrame = stackFrameDetails[0];

// Certain context changes (like stepping into the default value expression
// of a parameter) do not create a call stack frame. In order to represent
// this context change we create a fake call stack frame.
if (!string.IsNullOrEmpty(scriptExtent.File)
&& !PathUtils.IsPathEqual(scriptExtent.File, targetFrame.ScriptPath))
{
await debugInfoHandle.WaitAsync().ConfigureAwait(false);
try
{
targetFrame = new StackFrameDetails
{
ScriptPath = scriptExtent.File,
// Just use the last frame's variables since we don't have a
// good way to get real values.
AutoVariables = targetFrame.AutoVariables,
CommandVariables = targetFrame.CommandVariables,
// Ideally we'd get a real value here but since there's no real
// call stack frame for this, we'd need to replicate a lot of
// engine code.
FunctionName = "<ScriptBlock>",
};

StackFrameDetails[] newFrames = new StackFrameDetails[stackFrameDetails.Length + 1];
newFrames[0] = targetFrame;
stackFrameDetails.CopyTo(newFrames, 1);
stackFrameDetails = newFrames;
}
finally
{
debugInfoHandle.Release();
}
}

targetFrame.StartLineNumber = scriptExtent.StartLineNumber;
targetFrame.EndLineNumber = scriptExtent.EndLineNumber;
targetFrame.StartColumnNumber = scriptExtent.StartColumnNumber;
targetFrame.EndColumnNumber = scriptExtent.EndColumnNumber;
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ internal class StackFrameDetails
/// <summary>
/// Gets the name of the function where the stack frame occurred.
/// </summary>
public string FunctionName { get; private set; }
public string FunctionName { get; internal init; }

/// <summary>
/// Gets the start line number of the script where the stack frame occurred.
Expand Down Expand Up @@ -62,12 +62,12 @@ internal class StackFrameDetails
/// <summary>
/// Gets or sets the VariableContainerDetails that contains the auto variables.
/// </summary>
public VariableContainerDetails AutoVariables { get; private set; }
public VariableContainerDetails AutoVariables { get; internal init; }

/// <summary>
/// Gets or sets the VariableContainerDetails that contains the call stack frame variables.
/// </summary>
public VariableContainerDetails CommandVariables { get; private set; }
public VariableContainerDetails CommandVariables { get; internal init; }

#endregion

Expand Down
22 changes: 22 additions & 0 deletions src/PowerShellEditorServices/Utility/PathUtils.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System;
using System.IO;
using System.Management.Automation;
using System.Runtime.InteropServices;
Expand Down Expand Up @@ -29,13 +30,34 @@ internal static class PathUtils
/// </summary>
internal static readonly char AlternatePathSeparator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? '/' : '\\';

internal static readonly StringComparison PathComparison = RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
? StringComparison.Ordinal
: StringComparison.OrdinalIgnoreCase;

/// <summary>
/// Converts all alternate path separators to the current platform's main path separators.
/// </summary>
/// <param name="path">The path to normalize.</param>
/// <returns>The normalized path.</returns>
public static string NormalizePathSeparators(string path) => string.IsNullOrWhiteSpace(path) ? path : path.Replace(AlternatePathSeparator, DefaultPathSeparator);

internal static bool IsPathEqual(string left, string right)
{
if (string.IsNullOrEmpty(left))
{
return string.IsNullOrEmpty(right);
}

if (string.IsNullOrEmpty(right))
{
return false;
}

left = Path.GetFullPath(left).TrimEnd(DefaultPathSeparator);
right = Path.GetFullPath(right).TrimEnd(DefaultPathSeparator);
return left.Equals(right, PathComparison);
}

/// <summary>
/// Return the given path with all PowerShell globbing characters escaped,
/// plus optionally the whitespace.
Expand Down

0 comments on commit eb9069d

Please sign in to comment.