Skip to content

Commit eb9069d

Browse files
Add artifical stack frame to represent context
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.
1 parent c25d0d2 commit eb9069d

File tree

3 files changed

+64
-7
lines changed

3 files changed

+64
-7
lines changed

src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs

+39-4
Original file line numberDiff line numberDiff line change
@@ -994,10 +994,45 @@ await _remoteFileManager.FetchRemoteFileAsync(
994994
// Augment the top stack frame with details from the stop event
995995
if (invocationTypeScriptPositionProperty.GetValue(e.InvocationInfo) is IScriptExtent scriptExtent)
996996
{
997-
stackFrameDetails[0].StartLineNumber = scriptExtent.StartLineNumber;
998-
stackFrameDetails[0].EndLineNumber = scriptExtent.EndLineNumber;
999-
stackFrameDetails[0].StartColumnNumber = scriptExtent.StartColumnNumber;
1000-
stackFrameDetails[0].EndColumnNumber = scriptExtent.EndColumnNumber;
997+
StackFrameDetails targetFrame = stackFrameDetails[0];
998+
999+
// Certain context changes (like stepping into the default value expression
1000+
// of a parameter) do not create a call stack frame. In order to represent
1001+
// this context change we create a fake call stack frame.
1002+
if (!string.IsNullOrEmpty(scriptExtent.File)
1003+
&& !PathUtils.IsPathEqual(scriptExtent.File, targetFrame.ScriptPath))
1004+
{
1005+
await debugInfoHandle.WaitAsync().ConfigureAwait(false);
1006+
try
1007+
{
1008+
targetFrame = new StackFrameDetails
1009+
{
1010+
ScriptPath = scriptExtent.File,
1011+
// Just use the last frame's variables since we don't have a
1012+
// good way to get real values.
1013+
AutoVariables = targetFrame.AutoVariables,
1014+
CommandVariables = targetFrame.CommandVariables,
1015+
// Ideally we'd get a real value here but since there's no real
1016+
// call stack frame for this, we'd need to replicate a lot of
1017+
// engine code.
1018+
FunctionName = "<ScriptBlock>",
1019+
};
1020+
1021+
StackFrameDetails[] newFrames = new StackFrameDetails[stackFrameDetails.Length + 1];
1022+
newFrames[0] = targetFrame;
1023+
stackFrameDetails.CopyTo(newFrames, 1);
1024+
stackFrameDetails = newFrames;
1025+
}
1026+
finally
1027+
{
1028+
debugInfoHandle.Release();
1029+
}
1030+
}
1031+
1032+
targetFrame.StartLineNumber = scriptExtent.StartLineNumber;
1033+
targetFrame.EndLineNumber = scriptExtent.EndLineNumber;
1034+
targetFrame.StartColumnNumber = scriptExtent.StartColumnNumber;
1035+
targetFrame.EndColumnNumber = scriptExtent.EndColumnNumber;
10011036
}
10021037
}
10031038

src/PowerShellEditorServices/Services/DebugAdapter/Debugging/StackFrameDetails.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ internal class StackFrameDetails
3131
/// <summary>
3232
/// Gets the name of the function where the stack frame occurred.
3333
/// </summary>
34-
public string FunctionName { get; private set; }
34+
public string FunctionName { get; internal init; }
3535

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

6767
/// <summary>
6868
/// Gets or sets the VariableContainerDetails that contains the call stack frame variables.
6969
/// </summary>
70-
public VariableContainerDetails CommandVariables { get; private set; }
70+
public VariableContainerDetails CommandVariables { get; internal init; }
7171

7272
#endregion
7373

src/PowerShellEditorServices/Utility/PathUtils.cs

+22
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4+
using System;
45
using System.IO;
56
using System.Management.Automation;
67
using System.Runtime.InteropServices;
@@ -29,13 +30,34 @@ internal static class PathUtils
2930
/// </summary>
3031
internal static readonly char AlternatePathSeparator = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? '/' : '\\';
3132

33+
internal static readonly StringComparison PathComparison = RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
34+
? StringComparison.Ordinal
35+
: StringComparison.OrdinalIgnoreCase;
36+
3237
/// <summary>
3338
/// Converts all alternate path separators to the current platform's main path separators.
3439
/// </summary>
3540
/// <param name="path">The path to normalize.</param>
3641
/// <returns>The normalized path.</returns>
3742
public static string NormalizePathSeparators(string path) => string.IsNullOrWhiteSpace(path) ? path : path.Replace(AlternatePathSeparator, DefaultPathSeparator);
3843

44+
internal static bool IsPathEqual(string left, string right)
45+
{
46+
if (string.IsNullOrEmpty(left))
47+
{
48+
return string.IsNullOrEmpty(right);
49+
}
50+
51+
if (string.IsNullOrEmpty(right))
52+
{
53+
return false;
54+
}
55+
56+
left = Path.GetFullPath(left).TrimEnd(DefaultPathSeparator);
57+
right = Path.GetFullPath(right).TrimEnd(DefaultPathSeparator);
58+
return left.Equals(right, PathComparison);
59+
}
60+
3961
/// <summary>
4062
/// Return the given path with all PowerShell globbing characters escaped,
4163
/// plus optionally the whitespace.

0 commit comments

Comments
 (0)