From 8bb072ba83d16b16d46347e43ea2116547be97fa Mon Sep 17 00:00:00 2001 From: Jeffrey Stedfast Date: Tue, 7 Jan 2020 12:01:37 -0500 Subject: [VsCodeDebugger] code refactoring & performance improvements Cache IsEnum checks and do more validation that a variable/expression evaluation result is an enum before making an expensive debugger query to find out if the type is indeed an enum type. Fixes https://devdiv.visualstudio.com/DevDiv/_workitems/edit/1032065/ --- ...MonoDevelop.Debugger.VsCodeDebugProtocol.csproj | 2 + .../VSCodeDebuggerSession.cs | 46 ++- .../VSCodeEvaluationSource.cs | 118 ++++++ .../VSCodeVariableSource.cs | 76 ++++ .../VsCodeBacktrace.cs | 54 +-- .../VsCodeObjectSource.cs | 410 ++++++++++++--------- 6 files changed, 498 insertions(+), 208 deletions(-) create mode 100644 main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VSCodeEvaluationSource.cs create mode 100644 main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VSCodeVariableSource.cs (limited to 'main/src/addins') diff --git a/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol.csproj b/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol.csproj index abfc017abb..cffd4ddd46 100644 --- a/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol.csproj +++ b/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol.csproj @@ -48,6 +48,8 @@ + + diff --git a/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VSCodeDebuggerSession.cs b/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VSCodeDebuggerSession.cs index 5da7eda093..15d16030d7 100644 --- a/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VSCodeDebuggerSession.cs +++ b/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VSCodeDebuggerSession.cs @@ -23,20 +23,23 @@ // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. + using System; -using Mono.Debugging.Client; -using System.Diagnostics; -using Mono.Debugging.Backend; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; using System.IO; +using System.Linq; using System.Text; -using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages; +using System.Diagnostics; +using System.Threading.Tasks; +using System.Collections.Generic; + +using Mono.Debugging.Client; + using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol; -using System.Threading; +using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages; + using MonoDevelop.Core; using MonoDevelop.Core.Execution; + using MonoFunctionBreakpoint = Mono.Debugging.Client.FunctionBreakpoint; using VsCodeFunctionBreakpoint = Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages.FunctionBreakpoint; @@ -44,8 +47,23 @@ namespace MonoDevelop.Debugger.VsCodeDebugProtocol { public abstract class VSCodeDebuggerSession : DebuggerSession { + readonly Dictionary enumTypes = new Dictionary (); int currentThreadId; + internal bool IsEnum (string type, int frameId) + { + if (enumTypes.TryGetValue (type, out var isEnum)) + return isEnum; + + var request = new EvaluateRequest ($"typeof ({type}).IsEnum") { FrameId = frameId }; + var response = protocolClient.SendRequestSync (request); + + isEnum = response.Result.Equals ("true", StringComparison.OrdinalIgnoreCase); + enumTypes.Add (type, isEnum); + + return isEnum; + } + protected override void OnContinue () { protocolClient.SendRequestSync (new ContinueRequest (currentThreadId)); @@ -227,7 +245,7 @@ namespace MonoDevelop.Debugger.VsCodeDebugProtocol startInfo.StandardOutputEncoding = Encoding.UTF8; startInfo.StandardOutputEncoding = Encoding.UTF8; startInfo.UseShellExecute = false; - if (!MonoDevelop.Core.Platform.IsWindows) + if (!Platform.IsWindows) startInfo.EnvironmentVariables ["PATH"] = Environment.GetEnvironmentVariable ("PATH") + ":/usr/local/share/dotnet/"; debugAgentProcess = Process.Start (startInfo); debugAgentProcess.EnableRaisingEvents = true; @@ -236,7 +254,7 @@ namespace MonoDevelop.Debugger.VsCodeDebugProtocol protocolClient.RequestReceived += OnDebugAdaptorRequestReceived; protocolClient.Run (); protocolClient.EventReceived += HandleEvent; - InitializeRequest initRequest = CreateInitRequest (); + var initRequest = CreateInitRequest (); Capabilities = protocolClient.SendRequestSync (initRequest); } @@ -265,7 +283,7 @@ namespace MonoDevelop.Debugger.VsCodeDebugProtocol { pauseWhenFinished = !startInfo.CloseExternalConsoleOnExit; StartDebugAgent (); - LaunchRequest launchRequest = CreateLaunchRequest (startInfo); + var launchRequest = CreateLaunchRequest (startInfo); protocolClient.SendRequestSync (launchRequest); protocolClient.SendRequestSync (new ConfigurationDoneRequest ()); } @@ -316,7 +334,7 @@ namespace MonoDevelop.Debugger.VsCodeDebugProtocol bool? EvaluateCondition (int frameId, string exp) { - var response = protocolClient.SendRequestSync (new EvaluateRequest (exp, frameId)).Result; + var response = protocolClient.SendRequestSync (new EvaluateRequest (exp) { FrameId = frameId }).Result; if (bool.TryParse (response, out var result)) return result; @@ -455,7 +473,7 @@ namespace MonoDevelop.Debugger.VsCodeDebugProtocol } break; } - }); + }).Ignore (); } List pathsWithBreakpoints = new List (); @@ -516,7 +534,7 @@ namespace MonoDevelop.Debugger.VsCodeDebugProtocol if (obj.Breakpoints [i].Line != sourceFile.ElementAt (i).OriginalLine) breakpoints [sourceFile.ElementAt (i)].AdjustBreakpointLocation (obj.Breakpoints [i].Line, obj.Breakpoints [i].Column ?? 1); } - }); + }).Ignore (); }); } diff --git a/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VSCodeEvaluationSource.cs b/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VSCodeEvaluationSource.cs new file mode 100644 index 0000000000..54af40f0dc --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VSCodeEvaluationSource.cs @@ -0,0 +1,118 @@ +using System; +using System.Globalization; + +using Mono.Debugging.Client; +using Mono.Debugging.Evaluation; + +using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages; + +namespace MonoDevelop.Debugger.VsCodeDebugProtocol +{ + class VSCodeEvaluationSource : VSCodeObjectSource + { + readonly EvaluateResponse response; + readonly string expression; + readonly string display; + readonly string value; + readonly string name; + readonly string type; + + public VSCodeEvaluationSource (VSCodeDebuggerSession session, string expression, EvaluateResponse response, int frameId) : base (session, 0, frameId) + { + this.expression = expression; + this.response = response; + + // FIXME: can we use PresentationHint.Attributes == VariablePresentationHint.AttributesValue.FailedEvaluation instead? + if (response.Type == null) { + if (IsCSError (118, "is a namespace but is used like a variable", response.Result, out string ns)) { + Flags = ObjectValueFlags.Namespace; + display = name = value = ns; + type = ""; + return; + } + + if (IsCSError (119, "is a type, which is not valid in the given context", response.Result, out string vtype)) { + if (expression.StartsWith ("global::", StringComparison.Ordinal)) + vtype = expression.Substring ("global::".Length); + + display = name = value = ObjectValueAdaptor.GetCSharpTypeName (vtype); + Flags = ObjectValueFlags.Type; + type = ""; + return; + } + } + + var actualType = GetActualTypeName (response.Type); + + // FIXME: can we use VariablePresentationHint.AttributesValue.ReadOnly/Constant/Static etc for Flags? + Flags = ObjectValueFlags.ReadOnly; + type = actualType.Replace (", ", ","); + name = expression; + + if (actualType != "void") + value = GetFixedValue (response.Result, type, actualType); + else + value = "No return value."; + display = response.Result; + + if (name[0] == '[') + Flags |= ObjectValueFlags.ArrayElement; + + if (type == null || value == $"'{name}' threw an exception of type '{type}'") + Flags |= ObjectValueFlags.Error; + } + + protected override string Display { + get { return display; } + } + + protected override string Expression { + get { return expression; } + } + + protected override string Name { + get { return name; } + } + + protected override string Type { + get { return type; } + } + + protected override string Value { + get { return value; } + } + + protected override int VariablesReference { + get { return response.VariablesReference; } + } + + static bool IsCSError (int code, string message, string value, out string newValue) + { + var prefix = string.Format (CultureInfo.InvariantCulture, "error CS{0:D4}: '", code); + + newValue = null; + + if (value == null || !value.StartsWith (prefix, StringComparison.Ordinal)) + return false; + + int startIndex = prefix.Length; + int index = startIndex; + + while (index < value.Length && value [index] != '\'') + index++; + + newValue = value.Substring (startIndex, index - startIndex); + index++; + + if (index >= value.Length || value [index] != ' ') + return false; + + index++; + + if (index + message.Length != value.Length) + return false; + + return string.CompareOrdinal (value, index, message, 0, message.Length) == 0; + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VSCodeVariableSource.cs b/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VSCodeVariableSource.cs new file mode 100644 index 0000000000..a488f98f2c --- /dev/null +++ b/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VSCodeVariableSource.cs @@ -0,0 +1,76 @@ +using System; + +using Mono.Debugging.Client; + +using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages; + +namespace MonoDevelop.Debugger.VsCodeDebugProtocol +{ + class VSCodeVariableSource : VSCodeObjectSource + { + readonly Variable variable; + readonly string display; + readonly string value; + readonly string name; + readonly string type; + + public VSCodeVariableSource (VSCodeDebuggerSession session, Variable variable, int parentVariablesReference, int frameId) : base (session, parentVariablesReference, frameId) + { + this.variable = variable; + + var actualType = GetActualTypeName (variable.Type); + + // FIXME: can we use VariablePresentationHint.AttributesValue.ReadOnly/Constant/Static etc for Flags? + Flags = parentVariablesReference > 0 ? ObjectValueFlags.None : ObjectValueFlags.ReadOnly; + name = GetFixedVariableName (variable.Name); + type = actualType.Replace (", ", ","); + + if (actualType != "void") + value = GetFixedValue (variable.Value, type, actualType); + else + value = "No return value."; + display = variable.Value; + + if (name[0] == '[') + Flags |= ObjectValueFlags.ArrayElement; + + if (type == null || value == $"'{name}' threw an exception of type '{type}'") + Flags |= ObjectValueFlags.Error; + } + + protected override string Display { + get { return display; } + } + + protected override string Expression { + get { return variable.EvaluateName; } + } + + protected override string Name { + get { return name; } + } + + protected override string Type { + get { return type; } + } + + protected override string Value { + get { return value; } + } + + protected override int VariablesReference { + get { return variable.VariablesReference; } + } + + static string GetFixedVariableName (string name) + { + // Check for a type attribute and strip it off. + var index = name.LastIndexOf (" [", StringComparison.Ordinal); + + if (index != -1) + return name.Remove (index); + + return name; + } + } +} diff --git a/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VsCodeBacktrace.cs b/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VsCodeBacktrace.cs index d48abd184b..7fe678a1fe 100644 --- a/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VsCodeBacktrace.cs +++ b/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VsCodeBacktrace.cs @@ -2,13 +2,14 @@ using System; using System.Linq; using System.Collections.Generic; -using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages; - -using Mono.Debugging.Backend; using Mono.Debugging.Client; +using Mono.Debugging.Backend; + +using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages; using MonoDevelop.Core; +using StackFrame = Mono.Debugging.Client.StackFrame; using VsStackFrame = Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages.StackFrame; using VsFrameFormat = Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages.StackFrameFormat; @@ -74,8 +75,10 @@ namespace MonoDevelop.Debugger.VsCodeDebugProtocol continue; } - foreach (var variable in response.Variables) - results.Add (VsCodeVariableToObjectValue (session, variable, scope.VariablesReference, frame.Id)); + foreach (var variable in response.Variables) { + var source = new VSCodeVariableSource (session, variable, scope.VariablesReference, frame.Id); + results.Add (source.GetValue (default (ObjectPath), null)); + } timer.Success = true; } @@ -97,30 +100,25 @@ namespace MonoDevelop.Debugger.VsCodeDebugProtocol public ObjectValue [] GetExpressionValues (int frameIndex, string [] expressions, EvaluationOptions options) { var results = new List (); + var frame = frames[frameIndex]; + foreach (var expr in expressions) { using (var timer = session.EvaluationStats.StartTimer ()) { - var response = session.protocolClient.SendRequestSync (new EvaluateRequest (expr) { FrameId = frames[frameIndex].Id }); - results.Add (VsCodeVariableToObjectValue (session, expr, expr, response.Type, response.Result, response.VariablesReference, 0, frames [frameIndex].Id)); + var response = session.protocolClient.SendRequestSync (new EvaluateRequest (expr) { FrameId = frame.Id }); + var source = new VSCodeEvaluationSource (session, expr, response, frame.Id); + + results.Add (source.GetValue (default (ObjectPath), null)); timer.Success = true; } } - return results.ToArray (); - } - static ObjectValue VsCodeVariableToObjectValue (VSCodeDebuggerSession session, string name, string evalName, string type, string value, int variablesReference, int parentVariablesReference, int frameId) - { - return new VSCodeObjectSource (session, variablesReference, parentVariablesReference, name, type, evalName, frameId, value).GetValue (default (ObjectPath), null); - } - - internal static ObjectValue VsCodeVariableToObjectValue (VSCodeDebuggerSession session, Variable variable, int variablesReference, int frameId) - { - return VsCodeVariableToObjectValue (session, variable.Name, variable.EvaluateName, variable.Type, variable.Value, variable.VariablesReference, variablesReference, frameId); + return results.ToArray (); } ObjectValue[] GetVariables (int frameIndex, string scopeName) { var results = new List (); - var frame = frames [frameIndex]; + var frame = frames[frameIndex]; foreach (var scope in GetScopes (frameIndex)) { if (!scope.Name.Equals (scopeName, StringComparison.Ordinal)) @@ -137,8 +135,10 @@ namespace MonoDevelop.Debugger.VsCodeDebugProtocol continue; } - foreach (var variable in response.Variables) - results.Add (VsCodeVariableToObjectValue (session, variable, scope.VariablesReference, frame.Id)); + foreach (var variable in response.Variables) { + var source = new VSCodeVariableSource (session, variable, scope.VariablesReference, frame.Id); + results.Add (source.GetValue (default (ObjectPath), null)); + } timer.Success = true; } @@ -157,18 +157,18 @@ namespace MonoDevelop.Debugger.VsCodeDebugProtocol return GetVariables (frameIndex, "Arguments"); } - public Mono.Debugging.Client.StackFrame [] GetStackFrames (int firstIndex, int lastIndex) + public StackFrame [] GetStackFrames (int firstIndex, int lastIndex) { //Optimisation for getting 1st frame of thread(used for ThreadPad) - if (firstIndex == 0 && lastIndex == 1 && FrameCount > 0) { - return new Mono.Debugging.Client.StackFrame [] { new VsCodeStackFrame (this.format, threadId, 0, frames [0]) }; - } - var stackFrames = new Mono.Debugging.Client.StackFrame [Math.Min (lastIndex - firstIndex, FrameCount - firstIndex)]; + if (firstIndex == 0 && lastIndex == 1 && FrameCount > 0) + return new StackFrame[] { new VsCodeStackFrame (this.format, threadId, 0, frames[0]) }; + + var stackFrames = new StackFrame [Math.Min (lastIndex - firstIndex, FrameCount - firstIndex)]; var format = VsCodeStackFrame.GetStackFrameFormat (session.EvaluationOptions); var body = session.protocolClient.SendRequestSync (new StackTraceRequest (threadId) { StartFrame = firstIndex, Levels = stackFrames.Length, Format = format }); for (int i = 0; i < stackFrames.Length; i++) { - frames [i + firstIndex] = body.StackFrames [i]; - stackFrames [i] = new VsCodeStackFrame (format, threadId, i, body.StackFrames [i]); + frames[i + firstIndex] = body.StackFrames [i]; + stackFrames[i] = new VsCodeStackFrame (format, threadId, i, body.StackFrames [i]); } return stackFrames; } diff --git a/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VsCodeObjectSource.cs b/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VsCodeObjectSource.cs index 3da1def7ef..a8a84c86fb 100644 --- a/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VsCodeObjectSource.cs +++ b/main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VsCodeObjectSource.cs @@ -1,37 +1,71 @@ using System; -using System.Linq; using System.Text; using System.Globalization; +using System.Collections.Generic; -using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages; - -using Mono.Debugging.Backend; using Mono.Debugging.Client; -using Mono.Debugging.Evaluation; +using Mono.Debugging.Backend; + +using Microsoft.VisualStudio.Shared.VSCodeDebugProtocol.Messages; namespace MonoDevelop.Debugger.VsCodeDebugProtocol { - class VSCodeObjectSource : IObjectValueSource + abstract class VSCodeObjectSource : IObjectValueSource { - const VariablePresentationHint.AttributesValue ConstantReadOnlyStatic = VariablePresentationHint.AttributesValue.Constant | VariablePresentationHint.AttributesValue.ReadOnly | VariablePresentationHint.AttributesValue.Static; static readonly char[] CommaDotOrSquareEndBracket = { ',', '.', ']' }; static readonly char[] CommaOrSquareEndBracket = { ',', ']' }; static readonly char[] LessThanOrSquareBracket = { '<', '[' }; ObjectValue[] objValChildren; - readonly VSCodeDebuggerSession vsCodeDebuggerSession; - readonly int parentVariablesReference; - readonly ObjectValueFlags flags; - readonly int variablesReference; - readonly int frameId; - readonly string evalName; - readonly string display; - readonly string name; - readonly string type; - readonly string val; - - static string GetActualTypeName (string type) + protected VSCodeObjectSource (VSCodeDebuggerSession session, int parentVariablesReference, int frameId) + { + ParentVariablesReference = parentVariablesReference; + Session = session; + FrameId = frameId; + } + + protected abstract string Display { + get; + } + + protected abstract string Expression { + get; + } + + protected ObjectValueFlags Flags { + get; set; + } + + protected int FrameId { + get; private set; + } + + protected abstract string Name { + get; + } + + protected int ParentVariablesReference { + get; + } + + protected VSCodeDebuggerSession Session { + get; private set; + } + + protected abstract string Type { + get; + } + + protected abstract string Value { + get; + } + + protected abstract int VariablesReference { + get; + } + + protected static string GetActualTypeName (string type) { int startIndex; @@ -51,17 +85,6 @@ namespace MonoDevelop.Debugger.VsCodeDebugProtocol return type; } - static string GetFixedVariableName (string name) - { - // Check for a type attribute and strip it off. - var index = name.LastIndexOf (" [", StringComparison.Ordinal); - - if (index != -1) - return name.Remove (index); - - return name; - } - static bool IsMultiDimensionalArray (string type, out int arrayIndexer) { int index = type.IndexOfAny (LessThanOrSquareBracket); @@ -91,11 +114,56 @@ namespace MonoDevelop.Debugger.VsCodeDebugProtocol return index < type.Length && type[index] == ','; } + bool IsPotentialEnumValue (string value) + { + if (string.IsNullOrEmpty (value)) + return false; + + // Note: An enum value must begin with a letter + if (!char.IsLetter (value[0])) + return false; + + // Note: if the value has a '|', it's probably an enum (can it be anything else?) + if (value.IndexOf ('|') != -1) + return true; + + for (int i = 1; i < value.Length; i++) { + if (char.IsLetterOrDigit (value[i]) || value[i] == '_') + continue; + + return false; + } + + return true; + } + + bool IsEnum (string displayType, string value) + { + if (string.IsNullOrEmpty (displayType)) + return false; + + // Note: generic types cannot be enums + if (displayType[displayType.Length - 1] == '>') + return false; + + // Note: true and false look like enum values but aren't + if (displayType.Equals ("bool", StringComparison.Ordinal)) + return false; + + if (!IsPotentialEnumValue (value)) + return false; + + return Session.IsEnum (displayType, FrameId); + } + // Note: displayType will often have spaces after commas - string GetFixedValue (string value, string canonType, string displayType) + protected string GetFixedValue (string value, string canonType, string displayType) { int arrayIndex; + if (value.Equals ("null", StringComparison.Ordinal)) + return value; + if (IsMultiDimensionalArray (displayType, out arrayIndex)) { var arrayType = displayType.Substring (0, arrayIndex); var prefix = $"{{{arrayType}["; @@ -148,212 +216,220 @@ namespace MonoDevelop.Debugger.VsCodeDebugProtocol return compacted.ToString (); } - } else if (canonType == "char") { + } else if (canonType.Equals ("char", StringComparison.Ordinal)) { int startIndex = value.IndexOf ('\''); if (startIndex != -1) return value.Substring (startIndex); - } else { - var request = new EvaluateRequest ($"typeof ({displayType}).IsEnum") { FrameId = frameId }; - var result = vsCodeDebuggerSession.protocolClient.SendRequestSync (request); - - if (result.Result.Equals ("true", StringComparison.OrdinalIgnoreCase)) { - int endIndex = value.IndexOf (" | ", StringComparison.Ordinal); - - if (endIndex != -1) { - // The value a bitwise-or'd set of enum values - var expanded = new StringBuilder (); - int index = 0; + } else if (IsEnum (displayType, value)) { + int endIndex = value.IndexOf (" | ", StringComparison.Ordinal); - while (index < value.Length) { - endIndex = value.IndexOf (" | ", index, StringComparison.Ordinal); - string enumValue; + if (endIndex != -1) { + // The value is a bitwise-or'd set of enum values + var expanded = new StringBuilder (); + int index = 0; - if (endIndex != -1) - enumValue = value.Substring (index, endIndex - index); - else if (index > 0) - enumValue = value.Substring (index); - else - enumValue = value; + while (index < value.Length) { + endIndex = value.IndexOf (" | ", index, StringComparison.Ordinal); + string enumValue; - expanded.Append (canonType).Append ('.').Append (enumValue); + if (endIndex != -1) + enumValue = value.Substring (index, endIndex - index); + else if (index > 0) + enumValue = value.Substring (index); + else + enumValue = value; - if (endIndex == -1) - break; + expanded.Append (canonType).Append ('.').Append (enumValue); - expanded.Append (" | "); - index = endIndex + 3; - } + if (endIndex == -1) + break; - return expanded.ToString (); + expanded.Append (" | "); + index = endIndex + 3; } - return canonType + "." + value; + return expanded.ToString (); } + + return canonType + "." + value; } return value; } - static bool IsCSError (int code, string message, string value, out string newValue) + public ObjectValue[] GetChildren (ObjectPath path, int index, int count, EvaluationOptions options) { - var prefix = string.Format (CultureInfo.InvariantCulture, "error CS{0:D4}: '", code); - - newValue = null; - - if (value == null || !value.StartsWith (prefix, StringComparison.Ordinal)) - return false; - - int startIndex = prefix.Length; - int index = startIndex; - - while (index < value.Length && value[index] != '\'') - index++; - - newValue = value.Substring (startIndex, index - startIndex); - index++; - - if (index >= value.Length || value[index] != ' ') - return false; - - index++; + if (objValChildren == null) { + if (VariablesReference > 0) { + using (var timer = Session.EvaluationStats.StartTimer ()) { + var response = Session.protocolClient.SendRequestSync (new VariablesRequest (VariablesReference)); + var children = new List (); + + foreach (var variable in response.Variables) { + var source = new VSCodeVariableSource (Session, variable, VariablesReference, FrameId); + children.Add (source.GetValue (default (ObjectPath), null)); + } - if (index + message.Length != value.Length) - return false; + objValChildren = children.ToArray (); + timer.Success = true; + } + } else { + objValChildren = new ObjectValue[0]; + } + } - return string.CompareOrdinal (value, index, message, 0, message.Length) == 0; + return objValChildren; } - public VSCodeObjectSource (VSCodeDebuggerSession vsCodeDebuggerSession, int variablesReference, int parentVariablesReference, string name, string type, string evalName, int frameId, string val) + static string Quote (string text) { - this.vsCodeDebuggerSession = vsCodeDebuggerSession; - this.parentVariablesReference = parentVariablesReference; - this.variablesReference = variablesReference; - this.evalName = evalName; - this.frameId = frameId; - - if (type == null) { - if (IsCSError (118, "is a namespace but is used like a variable", val, out string ns)) { - this.display = this.name = this.val = ns; - this.flags = ObjectValueFlags.Namespace; - this.type = ""; - return; - } - - if (IsCSError (119, "is a type, which is not valid in the given context", val, out string vtype)) { - if (name.StartsWith ("global::", StringComparison.Ordinal)) - vtype = name.Substring ("global::".Length); - - this.display = this.name = this.val = ObjectValueAdaptor.GetCSharpTypeName (vtype); - this.flags = ObjectValueFlags.Type; - this.type = ""; - return; + var quoted = new StringBuilder (text.Length + 2); + + quoted.Append ('"'); + for (int i = 0; i < text.Length; i++) { + char c = text [i]; + + switch (c) { + case '\0': quoted.Append ("\\0"); break; + case '\a': quoted.Append ("\\a"); break; + case '\b': quoted.Append ("\\b"); break; + case '\n': quoted.Append ("\\n"); break; + case '\r': quoted.Append ("\\r"); break; + case '\t': quoted.Append ("\\t"); break; + case '\v': quoted.Append ("\\v"); break; + case '"': quoted.Append ("\\\""); break; + case '\\': quoted.Append ("\\\\"); break; + default: + if (c < ' ') { + quoted.AppendFormat (CultureInfo.InvariantCulture, "\\x{0:2}", c); + } else { + quoted.Append (c); + } + break; } } + quoted.Append ('"'); - var actualType = GetActualTypeName (type); - - this.flags = parentVariablesReference > 0 ? ObjectValueFlags.None : ObjectValueFlags.ReadOnly; - this.type = actualType.Replace (", ", ","); - this.name = GetFixedVariableName (name); - - if (actualType != "void") - this.val = GetFixedValue (val, this.type, actualType); - else - this.val = "No return value."; - this.display = val; - - if (this.name[0] == '[') - flags |= ObjectValueFlags.ArrayElement; - - if (type == null || val == $"'{this.name}' threw an exception of type '{this.type}'") - flags |= ObjectValueFlags.Error; + return quoted.ToString (); } - public ObjectValue[] GetChildren (ObjectPath path, int index, int count, EvaluationOptions options) + static string Unquote (string text) { - if (objValChildren == null) { - if (variablesReference <= 0) { - objValChildren = new ObjectValue[0]; - } else { - using (var timer = vsCodeDebuggerSession.EvaluationStats.StartTimer ()) { - var children = vsCodeDebuggerSession.protocolClient.SendRequestSync (new VariablesRequest ( - variablesReference - )).Variables; - objValChildren = children.Select (c => VSCodeDebuggerBacktrace.VsCodeVariableToObjectValue (vsCodeDebuggerSession, c, variablesReference, frameId)).ToArray (); - timer.Success = true; - } + var unquoted = new char [text.Length - 2]; + bool escaped = false; + int count = 0; + + for (int i = 1; i < text.Length - 1; i++) { + char c = text [i]; + + switch (c) { + case '\\': + if (escaped) + unquoted [count++] = '\\'; + escaped = !escaped; + break; + case '0': + unquoted [count++] = escaped ? '\0' : c; + escaped = false; + break; + case 'a': + unquoted [count++] = escaped ? '\a' : c; + escaped = false; + break; + case 'b': + unquoted [count++] = escaped ? '\b' : c; + escaped = false; + break; + case 'n': + unquoted [count++] = escaped ? '\n' : c; + escaped = false; + break; + case 'r': + unquoted [count++] = escaped ? '\r' : c; + escaped = false; + break; + case 't': + unquoted [count++] = escaped ? '\t' : c; + escaped = false; + break; + case 'v': + unquoted [count++] = escaped ? '\v' : c; + escaped = false; + break; + default: + unquoted [count++] = c; + escaped = false; + break; } } - return objValChildren; + + return new string (unquoted, 0, count); } class RawString : IRawValueString { - string val; - - public RawString (string val) + public RawString (string value) { - this.val = val.Remove (val.Length - 1).Remove (0, 1); + Value = value; } public int Length { - get { - return val.Length; - } + get { return Value.Length; } } public string Value { - get { - return val; - } + get; private set; } public string Substring (int index, int length) { - return val.Substring (index, length); + return Value.Substring (index, length); } } public object GetRawValue (ObjectPath path, EvaluationOptions options) { - string rawValue = null; + // Note: If the type is a string, then we already have the full value + if (Type.Equals ("string", StringComparison.Ordinal)) { + var rawValue = Unquote (Value); - using (var timer = vsCodeDebuggerSession.EvaluationStats.StartTimer ()) { - rawValue = vsCodeDebuggerSession.protocolClient.SendRequestSync (new EvaluateRequest (evalName) { FrameId = frameId }).Result; - timer.Success = true; + return new RawValueString (new RawString (rawValue)); } - if (rawValue.StartsWith ("\"", StringComparison.Ordinal)) { - if (options.ChunkRawStrings) - return new RawValueString (new RawString (rawValue)); - - return rawValue.Substring (1, rawValue.Length - 2); - } + //using (var timer = Session.EvaluationStats.StartTimer ()) { + // var response = Session.protocolClient.SendRequestSync (new EvaluateRequest (Expression) { FrameId = FrameId }); + // var rawValue = response.Result; + // timer.Success = true; + //} throw new NotImplementedException (); } public ObjectValue GetValue (ObjectPath path, EvaluationOptions options) { - if (val == "null") - return ObjectValue.CreateNullObject (this, name, type, flags); - if (variablesReference == 0)//This is some kind of primitive... - return ObjectValue.CreatePrimitive (this, new ObjectPath (name), type, new EvaluationResult (val, display), flags); - return ObjectValue.CreateObject (this, new ObjectPath (name), type, new EvaluationResult (val, display), flags, null); + if (Value == "null") + return ObjectValue.CreateNullObject (this, Name, Type, Flags); + + if (VariablesReference == 0) // This is some kind of primitive... + return ObjectValue.CreatePrimitive (this, new ObjectPath (Name), Type, new EvaluationResult (Value, Display), Flags); + + return ObjectValue.CreateObject (this, new ObjectPath (Name), Type, new EvaluationResult (Value, Display), Flags, null); } public void SetRawValue (ObjectPath path, object value, EvaluationOptions options) { var v = value.ToString (); - if (type == "string") - v = $"\"{v}\""; - vsCodeDebuggerSession.protocolClient.SendRequestSync (new SetVariableRequest (parentVariablesReference, name, v)); + + if (Type == "string") + v = Quote (v); + + Session.protocolClient.SendRequestSync (new SetVariableRequest (ParentVariablesReference, Name, v)); } public EvaluationResult SetValue (ObjectPath path, string value, EvaluationOptions options) { - return new EvaluationResult (vsCodeDebuggerSession.protocolClient.SendRequestSync (new SetVariableRequest (parentVariablesReference, name, value)).Value); + return new EvaluationResult (Session.protocolClient.SendRequestSync (new SetVariableRequest (ParentVariablesReference, Name, value)).Value); } } } -- cgit v1.2.3