From e4baf7416b4caa5db79ac83c66a0a70abf031c02 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Thu, 30 Mar 2017 20:07:42 -0700 Subject: [PATCH] Fix crash when breakpoint gets hit outside of debug mode This change fixes a crash that occurs when a breakpoint is hit at a time when the debugger is not active. This is caused by some event handlers being registered prematurely in the DebugAdapter before it gets activated by VS Code. Fixes PowerShell/vscode-powershell#620. --- .../Server/DebugAdapter.cs | 57 +++++++++++++++---- 1 file changed, 46 insertions(+), 11 deletions(-) diff --git a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs index b75334fd4..4a5ef7d76 100644 --- a/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs +++ b/src/PowerShellEditorServices.Protocol/Server/DebugAdapter.cs @@ -30,6 +30,7 @@ public class DebugAdapter : DebugAdapterBase private bool isAttachSession; private bool waitingForAttach; private string scriptToLaunch; + private bool enableConsoleRepl; private bool ownsEditorSession; private bool executionCompleted; private string arguments; @@ -44,9 +45,6 @@ public DebugAdapter(EditorSession editorSession, ChannelBase serverChannel) : base(serverChannel) { this.editorSession = editorSession; - this.editorSession.PowerShellContext.RunspaceChanged += this.powerShellContext_RunspaceChanged; - this.editorSession.DebugService.DebuggerStopped += this.DebugService_DebuggerStopped; - this.editorSession.PowerShellContext.DebuggerResumed += this.powerShellContext_DebuggerResumed; } public DebugAdapter( @@ -54,19 +52,22 @@ public DebugAdapter( ProfilePaths profilePaths, ChannelBase serverChannel, IEditorOperations editorOperations) + : this(hostDetails, profilePaths, serverChannel, editorOperations, false) + { + } + + public DebugAdapter( + HostDetails hostDetails, + ProfilePaths profilePaths, + ChannelBase serverChannel, + IEditorOperations editorOperations, + bool enableConsoleRepl) : base(serverChannel) { this.ownsEditorSession = true; this.editorSession = new EditorSession(); this.editorSession.StartDebugSession(hostDetails, profilePaths, editorOperations); - this.editorSession.PowerShellContext.RunspaceChanged += this.powerShellContext_RunspaceChanged; - this.editorSession.DebugService.DebuggerStopped += this.DebugService_DebuggerStopped; - this.editorSession.PowerShellContext.DebuggerResumed += this.powerShellContext_DebuggerResumed; - - // The assumption in this overload is that the debugger - // is running in UI-hosted mode, no terminal interface - this.editorSession.ConsoleService.OutputWritten += this.powerShellContext_OutputWritten; - this.outputDebouncer = new OutputDebouncer(this); + this.enableConsoleRepl = enableConsoleRepl; } protected override void Initialize() @@ -139,6 +140,8 @@ private async Task OnExecutionCompleted(Task executeTask) await this.outputDebouncer.Flush(); } + this.UnregisterEventHandlers(); + if (this.isAttachSession) { // Ensure the read loop is stopped @@ -198,6 +201,7 @@ protected override void Shutdown() { this.editorSession.PowerShellContext.RunspaceChanged -= this.powerShellContext_RunspaceChanged; this.editorSession.DebugService.DebuggerStopped -= this.DebugService_DebuggerStopped; + this.editorSession.PowerShellContext.DebuggerResumed -= this.powerShellContext_DebuggerResumed; if (this.ownsEditorSession) { @@ -238,6 +242,8 @@ protected async Task HandleLaunchRequest( LaunchRequestArguments launchParams, RequestContext requestContext) { + this.RegisterEventHandlers(); + // Set the working directory for the PowerShell runspace to the cwd passed in via launch.json. // In case that is null, use the the folder of the script to be executed. If the resulting // working dir path is a file path then extract the directory and use that. @@ -335,6 +341,8 @@ protected async Task HandleAttachRequest( { this.isAttachSession = true; + this.RegisterEventHandlers(); + // If there are no host processes to attach to or the user cancels selection, we get a null for the process id. // This is not an error, just a request to stop the original "attach to" request. // Testing against "undefined" is a HACK because I don't know how to make "Cancel" on quick pick loading @@ -454,6 +462,8 @@ protected async Task HandleDisconnectRequest( } else { + this.UnregisterEventHandlers(); + await requestContext.SendResult(null); await this.Stop(); } @@ -839,6 +849,31 @@ await this.SendEvent( }); } + private void RegisterEventHandlers() + { + this.editorSession.PowerShellContext.RunspaceChanged += this.powerShellContext_RunspaceChanged; + this.editorSession.DebugService.DebuggerStopped += this.DebugService_DebuggerStopped; + this.editorSession.PowerShellContext.DebuggerResumed += this.powerShellContext_DebuggerResumed; + + if (!this.enableConsoleRepl) + { + this.editorSession.ConsoleService.OutputWritten += this.powerShellContext_OutputWritten; + this.outputDebouncer = new OutputDebouncer(this); + } + } + + private void UnregisterEventHandlers() + { + this.editorSession.PowerShellContext.RunspaceChanged -= this.powerShellContext_RunspaceChanged; + this.editorSession.DebugService.DebuggerStopped -= this.DebugService_DebuggerStopped; + this.editorSession.PowerShellContext.DebuggerResumed -= this.powerShellContext_DebuggerResumed; + + if (!this.enableConsoleRepl) + { + this.editorSession.ConsoleService.OutputWritten -= this.powerShellContext_OutputWritten; + } + } + #endregion #region Event Handlers