diff --git a/src/OneScript.DebugProtocol/IDebuggerService.cs b/src/OneScript.DebugProtocol/IDebuggerService.cs index 84f7593f4..d9edb6cb6 100644 --- a/src/OneScript.DebugProtocol/IDebuggerService.cs +++ b/src/OneScript.DebugProtocol/IDebuggerService.cs @@ -17,7 +17,13 @@ public interface IDebuggerService /// Все точки останова уже установлены, все настройки сделаны /// void Execute(int threadId); - + + /// + /// Добавление фильтров точек останова для исплючений + /// + /// + void SetMachineExceptionBreakpoints((string Id, string Condition)[] filters); + /// /// Установка точек остановки /// diff --git a/src/OneScript.DebugServices/DefaultBreakpointManager.cs b/src/OneScript.DebugServices/DefaultBreakpointManager.cs index 9124efea6..09441a8f1 100644 --- a/src/OneScript.DebugServices/DefaultBreakpointManager.cs +++ b/src/OneScript.DebugServices/DefaultBreakpointManager.cs @@ -5,17 +5,26 @@ This Source Code Form is subject to the terms of the at http://mozilla.org/MPL/2.0/. ----------------------------------------------------------*/ +using System; using System.Collections.Generic; using System.Linq; +using OneScript.Commons; using ScriptEngine.Machine; namespace OneScript.DebugServices { public class DefaultBreakpointManager : IBreakpointManager { + private readonly Dictionary _exceptionBreakpointsFilters = new Dictionary(); private readonly List _breakpoints = new List(); private int _idsGenerator; + public void SetExceptionBreakpoints((string Id, string Condition)[] filters) + { + _exceptionBreakpointsFilters?.Clear(); + filters?.ForEach(c =>_exceptionBreakpointsFilters.Add(c.Id, c.Condition)); + } + public void SetBreakpoints(string module, (int Line, string Condition)[] breakpoints) { var cleaned = _breakpoints.Where(x => x.Module != module) @@ -27,10 +36,8 @@ public void SetBreakpoints(string module, (int Line, string Condition)[] breakpo _breakpoints.AddRange(cleaned); } - public bool Find(string module, int line) - { - return _breakpoints.Find(x => x.Module.Equals(module) && x.LineNumber == line) != null; - } + public bool FindBreakpoint(string module, int line) + => _breakpoints.Find(x => x.Module.Equals(module) && x.LineNumber == line) != null; public string GetCondition(string module, int line) => _breakpoints.Find(x => x.Module.Equals(module) && x.LineNumber == line).Condition; @@ -38,6 +45,26 @@ public string GetCondition(string module, int line) public void Clear() { _breakpoints.Clear(); + _exceptionBreakpointsFilters.Clear(); + } + + public bool StopOnAnyException(string message) + => NeedStopOnException("all", message); + + public bool StopOnUncaughtException(string message) + => NeedStopOnException("uncaught", message); + + private bool NeedStopOnException(string filterId, string message) + { + if (_exceptionBreakpointsFilters?.TryGetValue(filterId, out var condition) == true) + { + if (string.IsNullOrEmpty(condition)) + return true; + else + return message.Contains(condition); + } + + return false; } } } \ No newline at end of file diff --git a/src/OneScript.DebugServices/DefaultDebugService.cs b/src/OneScript.DebugServices/DefaultDebugService.cs index d7a8ac6a4..b4bfdd8b3 100644 --- a/src/OneScript.DebugServices/DefaultDebugService.cs +++ b/src/OneScript.DebugServices/DefaultDebugService.cs @@ -51,6 +51,15 @@ public void Execute(int threadId) } } + public void SetMachineExceptionBreakpoints((string Id, string Condition)[] filters) + { + _breakpointManager.SetExceptionBreakpoints(filters); + + // Уведомить все потоки о новых фильтрах + foreach (var machine in _threadManager.GetAllTokens().Select(x => x.Machine)) + machine.SetDebugMode(_breakpointManager); + } + public Breakpoint[] SetMachineBreakpoints(Breakpoint[] breaksToSet) { var confirmedBreakpoints = new List(); diff --git a/src/ScriptEngine/Machine/IDebugController.cs b/src/ScriptEngine/Machine/IDebugController.cs index 1a0524221..7ff1d74c6 100644 --- a/src/ScriptEngine/Machine/IDebugController.cs +++ b/src/ScriptEngine/Machine/IDebugController.cs @@ -24,9 +24,15 @@ public interface IDebugController : IDisposable public interface IBreakpointManager { + void SetExceptionBreakpoints((string Id, string Condition)[] filters); + void SetBreakpoints(string module, (int Line, string Condition)[] breakpoints); + + bool StopOnAnyException(string message); + + bool StopOnUncaughtException(string message); - bool Find(string module, int line); + bool FindBreakpoint(string module, int line); string GetCondition(string module, int line); diff --git a/src/ScriptEngine/Machine/MachineInstance.cs b/src/ScriptEngine/Machine/MachineInstance.cs index 1f7b21496..f5a9d447c 100644 --- a/src/ScriptEngine/Machine/MachineInstance.cs +++ b/src/ScriptEngine/Machine/MachineInstance.cs @@ -409,7 +409,14 @@ private void ExecuteCode() { SetScriptExceptionSource(exc); - if (ShouldRethrowException(exc)) + var shouldRethrow = ShouldRethrowException(exc); + + if (MachineStopped != null && _stopManager != null) + if (_stopManager.Breakpoints.StopOnAnyException(exc.MessageWithoutCodeFragment) || + shouldRethrow && _stopManager.Breakpoints.StopOnUncaughtException(exc.MessageWithoutCodeFragment)) + EmitStopOnException(); + + if (shouldRethrow) throw; } } @@ -1297,6 +1304,16 @@ private void LineNum(int arg) NextInstruction(); } + private void EmitStopOnException() + { + if (MachineStopped != null && _stopManager != null) + { + CreateFullCallstack(); + var args = new MachineStoppedEventArgs(MachineStopReason.Exception, Environment.CurrentManagedThreadId, ""); + MachineStopped?.Invoke(this, args); + } + } + private void EmitStopEventIfNecessary() { if (MachineStopped != null && _stopManager != null && _stopManager.ShouldStopAtThisLine(_module.Source.Location, _currentFrame)) diff --git a/src/ScriptEngine/Machine/MachineStopManager.cs b/src/ScriptEngine/Machine/MachineStopManager.cs index c8b1c3359..fcb5b2aa6 100644 --- a/src/ScriptEngine/Machine/MachineStopManager.cs +++ b/src/ScriptEngine/Machine/MachineStopManager.cs @@ -19,8 +19,6 @@ internal enum DebugState SteppingOut } - - internal class MachineStopManager { private struct StopPoint @@ -119,7 +117,7 @@ public bool ShouldStopAtThisLine(string module, ExecutionFrame currentFrame) private bool HitBreakpointOnLine(string module, ExecutionFrame currentFrame) { - return _breakpoints.Find(module, currentFrame.LineNumber); + return _breakpoints.FindBreakpoint(module, currentFrame.LineNumber); } private bool FrameIsInStopList(ExecutionFrame currentFrame) diff --git a/src/VSCode.DebugAdapter/DebugSession.cs b/src/VSCode.DebugAdapter/DebugSession.cs index ced5ff0e3..85fd8faf9 100644 --- a/src/VSCode.DebugAdapter/DebugSession.cs +++ b/src/VSCode.DebugAdapter/DebugSession.cs @@ -118,7 +118,13 @@ public class Breakpoint public bool verified { get; } public int line { get; } - public Breakpoint(bool verified, int line) { + public Breakpoint(bool verified) + { + this.verified = verified; + line = 0; + } + + public Breakpoint(bool verified, int line) { this.verified = verified; this.line = line; } @@ -180,6 +186,7 @@ public class Capabilities : ResponseBody { public bool supportsFunctionBreakpoints; public bool supportsConditionalBreakpoints; public bool supportsEvaluateForHovers; + public bool supportsExceptionFilterOptions; public dynamic[] exceptionBreakpointFilters; public bool supportTerminateDebuggee; } @@ -262,15 +269,28 @@ public class SetBreakpointsResponseBody : ResponseBody public SetBreakpointsResponseBody(List bpts = null) { if (bpts == null) - breakpoints = new Breakpoint[0]; - else + breakpoints = Array.Empty(); + else breakpoints = bpts.ToArray(); } } - // ---- The Session -------------------------------------------------------- + public class SetExceptionBreakpointsResponseBody : ResponseBody + { + public Breakpoint[] breakpoints { get; } + + public SetExceptionBreakpointsResponseBody(List bpts = null) + { + if (bpts == null) + breakpoints = Array.Empty(); + else + breakpoints = bpts.ToArray(); + } + } + + // ---- The Session -------------------------------------------------------- - public abstract class DebugSession : ProtocolServer + public abstract class DebugSession : ProtocolServer { private bool _clientLinesStartAt1 = true; private bool _clientPathsAreUri = true; diff --git a/src/VSCode.DebugAdapter/DebugeeProcess.cs b/src/VSCode.DebugAdapter/DebugeeProcess.cs index a34ec438f..61769232f 100644 --- a/src/VSCode.DebugAdapter/DebugeeProcess.cs +++ b/src/VSCode.DebugAdapter/DebugeeProcess.cs @@ -201,6 +201,11 @@ public void Kill() _process.WaitForExit(1500); } + public void SetExceptionsBreakpoints((string Id, string Condition)[] filters) + { + _debugger.SetMachineExceptionBreakpoints(filters); + } + public Breakpoint[] SetBreakpoints(IEnumerable breakpoints) { var confirmedBreaks = _debugger.SetMachineBreakpoints(breakpoints.ToArray()); diff --git a/src/VSCode.DebugAdapter/OscriptDebugSession.cs b/src/VSCode.DebugAdapter/OscriptDebugSession.cs index 327db85e4..cea13a8b2 100644 --- a/src/VSCode.DebugAdapter/OscriptDebugSession.cs +++ b/src/VSCode.DebugAdapter/OscriptDebugSession.cs @@ -9,9 +9,11 @@ This Source Code Form is subject to the terms of the using System.IO; using System.Runtime.CompilerServices; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using OneScript.DebugProtocol; using Serilog; using VSCodeDebug; +using static System.Net.WebRequestMethods; namespace VSCode.DebugAdapter @@ -43,7 +45,26 @@ public override void Initialize(Response response, dynamic args) supportsConditionalBreakpoints = true, supportsFunctionBreakpoints = false, supportsConfigurationDoneRequest = true, - exceptionBreakpointFilters = new dynamic[0], + supportsExceptionFilterOptions = true, + exceptionBreakpointFilters = new dynamic[] + { + new + { + filter = "uncaught", + label = "Необработанные исключения", + description = "Остановка при возникновении необработанного исключения", + supportsCondition = true, + conditionDescription = "Искомая подстрока текста исключения" + }, + new + { + filter = "all", + label = "Все исключения", + description = "Остановка при возникновении любого исключения", + supportsCondition = true, + conditionDescription = "Искомая подстрока текста исключения" + } + }, supportsEvaluateForHovers = true, supportTerminateDebuggee = true }); @@ -113,7 +134,7 @@ public override void Launch(Response response, dynamic args) public override void Attach(Response response, dynamic arguments) { LogCommandReceived(); - _process.DebugPort = getInt(arguments, "debugPort", 2801); + _process.DebugPort = GetFromContainer(arguments, "debugPort", 2801); _process.ProcessExited += (s, e) => { Log.Information("Debuggee has exited"); @@ -148,6 +169,31 @@ public override void Disconnect(Response response, dynamic arguments) SendResponse(response); } + public override void SetExceptionBreakpoints(Response response, dynamic arguments) + { + LogCommandReceived(arguments); + Log.Debug("Exception breakpoints: {Data}", JsonConvert.SerializeObject(arguments)); + + var acceptedFilters = new List(); + var filters = new List<(string Id, string Condition)>(); + + foreach(var filter in arguments.filters) + { + filters.Add((filter, "")); + acceptedFilters.Add(new VSCodeDebug.Breakpoint(true)); + } + + foreach (var filterOption in arguments.filterOptions) + { + filters.Add((filterOption.filterId, filterOption.condition ?? "")); + acceptedFilters.Add(new VSCodeDebug.Breakpoint(true)); + } + + _process.SetExceptionsBreakpoints(filters.ToArray()); + + SendResponse(response, new SetExceptionBreakpointsResponseBody(acceptedFilters)); + } + public override void SetBreakpoints(Response response, dynamic arguments) { LogCommandReceived(); @@ -322,7 +368,7 @@ public override void StackTrace(Response response, dynamic arguments) public override void Scopes(Response response, dynamic arguments) { LogCommandReceived(); - int frameId = getInt(arguments, "frameId"); + int frameId = GetFromContainer(arguments, "frameId", 0); var frame = _framesHandles.Get(frameId, null); if (frame == null) { @@ -338,7 +384,7 @@ public override void Scopes(Response response, dynamic arguments) public override void Variables(Response response, dynamic arguments) { LogCommandReceived(); - int varsHandle = getInt(arguments, "variablesReference"); + int varsHandle = GetFromContainer(arguments, "variablesReference", 0); var variables = _variableHandles.Get(varsHandle, null); if (variables == null) { @@ -386,7 +432,7 @@ public override void Evaluate(Response response, dynamic arguments) { LogCommandReceived(); // expression, frameId, context - int frameId = getInt(arguments, "frameId"); + int frameId = GetFromContainer(arguments, "frameId", 0); var frame = _framesHandles.Get(frameId, null); if (frame == null) { @@ -438,11 +484,11 @@ private void SendOutput(string category, string data) } } - private static int getInt(dynamic container, string propertyName, int dflt = 0) + private static T GetFromContainer(dynamic container, string propertyName, T dflt = default) { try { - return (int)container[propertyName]; + return (T)container[propertyName]; } catch (Exception) { diff --git a/src/VSCode.DebugAdapter/OscriptProtocols/Tcp/TcpDebugServerClient.cs b/src/VSCode.DebugAdapter/OscriptProtocols/Tcp/TcpDebugServerClient.cs index 27a7165d7..6aa5f131e 100644 --- a/src/VSCode.DebugAdapter/OscriptProtocols/Tcp/TcpDebugServerClient.cs +++ b/src/VSCode.DebugAdapter/OscriptProtocols/Tcp/TcpDebugServerClient.cs @@ -133,6 +133,11 @@ public void Execute(int threadId) WriteCommand(threadId); } + public void SetMachineExceptionBreakpoints((string Id, string Condition)[] filters) + { + WriteCommand(filters); + } + public Breakpoint[] SetMachineBreakpoints(Breakpoint[] breaksToSet) { WriteCommand(breaksToSet);