-
Notifications
You must be signed in to change notification settings - Fork 226
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve debugger's variable population mechanism #1620
Improve debugger's variable population mechanism #1620
Conversation
Thoughts: Do we need to do any kind of additional verification to make sure the variable "scope" matches the stacktrace? Or is this lazy iterator method considered pretty consistent? |
Might be worth looking into whether |
@SeeminglyScience could be because the stack frames are being returned deserialized here, maybe some more formatting can go into the Get-PSCallStack request? @daviwil any recollection? |
Another Q: Any reason why this is generic'd to |
Oh, the reason may be when debugging remote runspaces, need to test that. |
I'll adjust the pscommand here to return the variable info as well so the subsequent queries are unnecessary. |
I know Rob spent a good chunk of time making sure this worked, though sadly there wasn't really a way to put tests around it. |
If it's not already in a big old comment explaining so, would you mind adding @JustinGrote? |
Oh yeah I'm sure how it's set up works fine for remote runspaces. It's just my |
src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs
Outdated
Show resolved
Hide resolved
This now uses serialization only for the remote runspace pathway. I'm not very happy about all the "isOnRemoteMachine" ternaries, this should probably instead be abstracted out to a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Awesome work, thanks for doing this! ❤️ Got a few requests and questions
src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs
Outdated
Show resolved
Hide resolved
src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs
Outdated
Show resolved
Hide resolved
src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs
Outdated
Show resolved
Hide resolved
src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs
Outdated
Show resolved
Hide resolved
src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs
Outdated
Show resolved
Hide resolved
src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs
Outdated
Show resolved
Hide resolved
src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs
Outdated
Show resolved
Hide resolved
src/PowerShellEditorServices/Services/DebugAdapter/DebugService.cs
Outdated
Show resolved
Hide resolved
src/PowerShellEditorServices/Services/DebugAdapter/Debugging/VariableDetails.cs
Outdated
Show resolved
Hide resolved
…ences not matching up
Co-authored-by: Patrick Meinecke <SeeminglyScience@users.noreply.github.com>
Co-authored-by: Patrick Meinecke <SeeminglyScience@users.noreply.github.com>
@andschwa should be good to merge if the CI tests pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We're having to go back to the drawing board on this after testing. We accidentally dropped all the local variables 😅
Per discussion looks like |
😭 |
OK, new plan:
We will be able to vastly simplify things this way, and it will be more obvious and presentable to users as it would be what they would expect (e.g. these are the variables that can be seen at script scope, not the "script scope" variables, so it would include globals) Initially the auto pull forward vars will be hardcoded but I want to make this a setting. |
OK this is much better now. Auto now includes only $args and $PSItem (as the original code was meant to it seems) but it comes directly from the callstack frame, so if you have a deep callstack, it will show $args and $PSItem at the level of that callstack. Local/Script/Global basically reflect what is shown in get-variable, with script and local respectively de-duped as they are processed (this is also original behavior). The only difference in behavior is that local does not change with the stack layer, because stack layers and scopes are not 1:1 pairings. Instead, a future PR should add "Scope x" variable headings for the top stack frame only as with the current implementation it is not possible to accurately correlate scope layers to stack frames (may be possible in the future) Also updated so "Auto" shows both $_ and $PSItem even though they represent the same thing. @SeeminglyScience and I discussed and while it would be nice to only present $PSItem to encourage people to use it, that may confuse people who still use or learn $_ and are ignorant that they are the same. |
src/PowerShellEditorServices/Services/DebugAdapter/Debugging/StackFrameDetails.cs
Show resolved
Hide resolved
results = await _executionService.ExecutePSCommandAsync<PSObject>(psCommand, CancellationToken.None) | ||
.ConfigureAwait(false); | ||
} | ||
catch (CmdletInvocationException ex) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add a comment as to why this exception is caught and eaten here, except for 'psargumentoutofrangeexception' types.
// If we're attached to a remote runspace, we need to serialize the callstack prior to transport | ||
// because the default depth is too shallow | ||
bool isOnRemoteMachine = _psesHost.CurrentRunspace.IsOnRemoteMachine; | ||
string returnSerializedIfOnRemoteMachine = isOnRemoteMachine |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a bit confusing for future maintainers. Please add comments that clearly show the Get-PSCallStack
script that is run for both local and remote (serialized results) cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
string returnSerializedIfOnRemoteMachine = isOnRemoteMachine | |
// The next command always calls `Get-PSCallStack`. On a local machine, we just return | |
// its results. On a remote machine we serialize it first and then later deserialize it. | |
string returnSerializedIfOnRemoteMachine = isOnRemoteMachine |
Suggested comment to resolve Paul's request.
|
||
// This glorious hack ensures that Get-PSCallStack returns a list of CallStackFrame | ||
// objects (or "deserialized" CallStackFrames) when attached to a runspace in another | ||
// process. Without the intermediate variable Get-PSCallStack inexplicably returns | ||
// an array of strings containing the formatted output of the CallStackFrame list. | ||
var callStackVarName = $"$global:{PsesGlobalVariableNamePrefix}CallStack"; | ||
psCommand.AddScript($"{callStackVarName} = Get-PSCallStack; {callStackVarName}"); | ||
string callStackVarName = $"$global:{PsesGlobalVariableNamePrefix}CallStack"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be reviewed for possible injection opportunities, and ensure that arbitrary script content cannot be injected from external sources. ExecutePSCommandAsync
appears to really be 'ExecuteScriptAsync' since it takes a general script string instead of command plus arguments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While true, the only change here is from var
to string
. We can do this later.
Ok the only thing I'd like to know if we can still keep working (even admitting that it's funky) is to have the Auto variable groupset update per stack frame. In the (admittedly broken in several ways) current version of the code, if you hit a breakpoint and click through the frames in the callstack, the current frame's local and auto variables update in the VS Code debugger. I'll play with this and see if I can get it to work. |
Auto variable does update per stack frame for the auto variables, or at least it did in my testing. Local and Global do not because, as discussed, there is no actual correlary between stack frames and scopes, and the previous behavior that enabled this (upcycling through the scopes via -scope 0, -scope 1, etc.) was inaccurate especially if done in a module scope, and presenting inaccurate variables are considered worse than not presenting them at all. Rather than try to edit Local and Global, it would be better to have a "Stack" or "CurrentFrame" group list all variables in GetFrameVariables. |
Auto is not in my testing.
Yup, yup, no I get that there isn't a precise correlation and that it doesn't hold up across the board. It's annoying because there's at least some correlation in that the top stack frame is scope 0, and its parent scope is (most likely? always? at least a vast majority of the time?) scope 1...but yeah ok, you're right with "presenting inaccurate variables are considered worse than not presenting them at all." I think I agree, I just wish there was a better solution.
Well and this won't work well at all because we found Ugh, ok, this is an improvement and a bug fix. Let me see if the |
} | ||
catch (CmdletInvocationException ex) | ||
{ | ||
if (!ex.ErrorRecord.CategoryInfo.Reason.Equals("PSArgumentOutOfRangeException")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if (!ex.ErrorRecord.CategoryInfo.Reason.Equals("PSArgumentOutOfRangeException")) | |
// It's possible to be asked to run `Get-Variable -Scope N` where N is a number that | |
// exceeds the available scopes. In this case, the command throws this exception, | |
// but there's nothing we can do about it and we shouldn't crash the debugger, so we | |
// just return no results instead. All other exceptions should be thrown again. | |
if (!ex.ErrorRecord.CategoryInfo.Reason.Equals("PSArgumentOutOfRangeException")) |
Suggested comment to resolve Paul's request.
|
||
// This glorious hack ensures that Get-PSCallStack returns a list of CallStackFrame | ||
// objects (or "deserialized" CallStackFrames) when attached to a runspace in another | ||
// process. Without the intermediate variable Get-PSCallStack inexplicably returns | ||
// an array of strings containing the formatted output of the CallStackFrame list. | ||
var callStackVarName = $"$global:{PsesGlobalVariableNamePrefix}CallStack"; | ||
psCommand.AddScript($"{callStackVarName} = Get-PSCallStack; {callStackVarName}"); | ||
string callStackVarName = $"$global:{PsesGlobalVariableNamePrefix}CallStack"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While true, the only change here is from var
to string
. We can do this later.
// If we're attached to a remote runspace, we need to serialize the callstack prior to transport | ||
// because the default depth is too shallow | ||
bool isOnRemoteMachine = _psesHost.CurrentRunspace.IsOnRemoteMachine; | ||
string returnSerializedIfOnRemoteMachine = isOnRemoteMachine |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
string returnSerializedIfOnRemoteMachine = isOnRemoteMachine | |
// The next command always calls `Get-PSCallStack`. On a local machine, we just return | |
// its results. On a remote machine we serialize it first and then later deserialize it. | |
string returnSerializedIfOnRemoteMachine = isOnRemoteMachine |
Suggested comment to resolve Paul's request.
@andschwa example of the auto variables updating thru the stack during a Pester Test on PoshNmap. Remember we only currently capture $args and $_/$PSItem to Auto, we can either expand this to include all "current" stack variables that aren't some common noisy ones, or put them in another variable pane named "CurrentFrame" Capture.mp4 |
@JustinGrote I'm not seeing any auto variables now. I've built your branch (and double checked it's up-to-date except rebased on master, and that my extension is using the right build through the symlink). I'm in a Debug configuration. How are you testing? Also FYI I'm on macOS with PowerShell 7.2. |
Closing in favor of #1630 |
Fixes PowerShell/vscode-powershell#3667
Fixes PowerShell/vscode-powershell#3673