diff options
Diffstat (limited to 'main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VsCodeObjectSource.cs')
-rw-r--r-- | main/src/addins/MonoDevelop.Debugger.VSCodeDebugProtocol/MonoDevelop.Debugger.VsCodeDebugProtocol/VsCodeObjectSource.cs | 410 |
1 files changed, 243 insertions, 167 deletions
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<ObjectValue> (); + + 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 = "<namespace>"; - 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 = "<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); } } } |