From 46833d467f036e4ef9b72d5b34974d5830801bd9 Mon Sep 17 00:00:00 2001 From: Yoshi Askharoun Date: Sat, 20 Jan 2024 20:32:30 -0600 Subject: [PATCH] UIX debugger improvements --- UIX/Microsoft/Iris/Debug/BsonFormatter.cs | 4 +- UIX/Microsoft/Iris/Debug/Data/Breakpoint.cs | 14 ++- .../Iris/Debug/Data/DebuggerMessageFrame.cs | 97 ++++++++++++++++--- .../Iris/Debug/Data/MarkupLineNumberEntry.cs | 5 +- UIX/Microsoft/Iris/Debug/DebugRemoting.cs | 4 +- UIX/Microsoft/Iris/Debug/DebugSettings.cs | 2 +- .../Iris/Debug/DebuggerMessageType.cs | 2 + UIX/Microsoft/Iris/Debug/IDebuggerClient.cs | 6 ++ UIX/Microsoft/Iris/Debug/IDebuggerServer.cs | 5 + .../Iris/Debug/SystemNet/NetDebuggerClient.cs | 46 +++++++-- .../Iris/Debug/SystemNet/NetDebuggerServer.cs | 33 +++++-- .../Iris/Markup/MarkupLineNumberTable.cs | 6 +- 12 files changed, 181 insertions(+), 43 deletions(-) diff --git a/UIX/Microsoft/Iris/Debug/BsonFormatter.cs b/UIX/Microsoft/Iris/Debug/BsonFormatter.cs index f31d10f..a705566 100644 --- a/UIX/Microsoft/Iris/Debug/BsonFormatter.cs +++ b/UIX/Microsoft/Iris/Debug/BsonFormatter.cs @@ -61,7 +61,7 @@ internal class BsonFormatter : IFormatter var package = (JObject)serializer.Deserialize(reader); TypeName = package["0"].Value(); - SerializedObject = (JObject)package["1"]; + SerializedObject = package["1"]; // If the type is accessible from the current domain, // create an instance of it. @@ -72,7 +72,7 @@ internal class BsonFormatter : IFormatter public string TypeName { get; set; } - public JObject SerializedObject { get; set; } + public JToken SerializedObject { get; set; } public object Object { get; set; } } diff --git a/UIX/Microsoft/Iris/Debug/Data/Breakpoint.cs b/UIX/Microsoft/Iris/Debug/Data/Breakpoint.cs index e2c69ad..04aafe1 100644 --- a/UIX/Microsoft/Iris/Debug/Data/Breakpoint.cs +++ b/UIX/Microsoft/Iris/Debug/Data/Breakpoint.cs @@ -52,12 +52,18 @@ public struct Breakpoint : IEquatable public override bool Equals(object obj) => obj is Breakpoint bp && Equals(bp); - public override string ToString() + public override string ToString() => ToString(true); + + public string ToString(bool includeEnabled) { StringBuilder sb = new(); - sb.Append(Enabled ? '+' : '-'); - sb.Append(' '); + if (includeEnabled) + { + sb.Append(Enabled ? '+' : '-'); + sb.Append(' '); + } + sb.Append(Uri); sb.Append(' '); @@ -69,5 +75,5 @@ public struct Breakpoint : IEquatable return sb.ToString(); } - public override int GetHashCode() => ToString().GetHashCode(); + public override int GetHashCode() => ToString(false).GetHashCode(); } diff --git a/UIX/Microsoft/Iris/Debug/Data/DebuggerMessageFrame.cs b/UIX/Microsoft/Iris/Debug/Data/DebuggerMessageFrame.cs index 18445c7..7c3a9f9 100644 --- a/UIX/Microsoft/Iris/Debug/Data/DebuggerMessageFrame.cs +++ b/UIX/Microsoft/Iris/Debug/Data/DebuggerMessageFrame.cs @@ -1,28 +1,38 @@ using System; using System.IO; using System.Runtime.Serialization; -using System.Text; namespace Microsoft.Iris.Debug.Data; public class DebuggerMessageFrame { - public DebuggerMessageFrame() { } + private readonly IFormatter _formatter; + private object _value; + private byte[] _data; - public DebuggerMessageFrame(long transactionId, DebuggerMessageType type, byte[] data) + public DebuggerMessageFrame(long transactionId, DebuggerMessageType type, IFormatter formatter) { + _formatter = formatter; TransactionId = transactionId; Type = type; + } + + public DebuggerMessageFrame(long transactionId, DebuggerMessageType type, object value, IFormatter formatter) + : this(transactionId, type, formatter) + { + Value = value; + } + + public DebuggerMessageFrame(long transactionId, DebuggerMessageType type, byte[] data, IFormatter formatter) + : this(transactionId, type, formatter) + { Data = data; } - public DebuggerMessageFrame(long transactionId, DebuggerMessageType type, string message, Encoding encoding = null) - : this(transactionId, type, (encoding ?? Encoding.UTF8).GetBytes(message)) + public DebuggerMessageFrame(byte[] bytes, IFormatter formatter) { - } + _formatter = formatter; - public DebuggerMessageFrame(byte[] bytes) - { TransactionId = BitConverter.ToInt64(bytes, 0); Type = (DebuggerMessageType)BitConverter.ToInt32(bytes, sizeof(long)); @@ -39,7 +49,46 @@ public class DebuggerMessageFrame public DebuggerMessageType Type { get; set; } - public byte[] Data { get; set; } + public object Value + { + get + { + if (_value is null) + { + if (_data is null) throw new ArgumentException("Either a value or data must be specified."); + + using MemoryStream stream = new(_data); + _value = _formatter.Deserialize(stream); + } + + return _value; + } + set + { + _value = value; + _data = null; + } + } + + public byte[] Data + { + get + { + if (_data is null) + { + if (_value is null) throw new ArgumentException("Either a value or data must be specified."); + + _data = _value.Serialize(_formatter); + } + + return _data; + } + set + { + _data = value; + _value = default; + } + } public byte[] ToBytes() { @@ -62,11 +111,33 @@ public class DebuggerMessageFrame return bytes; } - public string GetDataAsString(Encoding encoding = null) => (encoding ?? Encoding.UTF8).GetString(Data); - - public T DeserializeData(IFormatter formatter) + public virtual T GetValue(IFormatter formatter = null) { using MemoryStream stream = new(Data); - return (T)formatter.Deserialize(stream); + return (T)(formatter ?? _formatter).Deserialize(stream); } + + public DebuggerMessageFrame Deserialize(IFormatter formatter = null) + => new(TransactionId, Type, GetValue(formatter), formatter ?? _formatter); +} + +public class DebuggerMessageFrame : DebuggerMessageFrame +{ + public DebuggerMessageFrame(byte[] bytes, IFormatter formatter) : base(bytes, formatter) + { + } + + public DebuggerMessageFrame(long transactionId, DebuggerMessageType type, IFormatter formatter) : base(transactionId, type, formatter) + { + } + + public DebuggerMessageFrame(long transactionId, DebuggerMessageType type, T value, IFormatter formatter) : base(transactionId, type, value, formatter) + { + } + + public DebuggerMessageFrame(long transactionId, DebuggerMessageType type, byte[] data, IFormatter formatter) : base(transactionId, type, data, formatter) + { + } + + public T GetValue() => (T)Value; } diff --git a/UIX/Microsoft/Iris/Debug/Data/MarkupLineNumberEntry.cs b/UIX/Microsoft/Iris/Debug/Data/MarkupLineNumberEntry.cs index e5e6fab..40bc75c 100644 --- a/UIX/Microsoft/Iris/Debug/Data/MarkupLineNumberEntry.cs +++ b/UIX/Microsoft/Iris/Debug/Data/MarkupLineNumberEntry.cs @@ -1,3 +1,6 @@ -namespace Microsoft.Iris.Debug.Data; +using System; +namespace Microsoft.Iris.Debug.Data; + +[Serializable] public record struct MarkupLineNumberEntry(uint Offset, int Line, int Column); diff --git a/UIX/Microsoft/Iris/Debug/DebugRemoting.cs b/UIX/Microsoft/Iris/Debug/DebugRemoting.cs index 287cba0..2d6e9b2 100644 --- a/UIX/Microsoft/Iris/Debug/DebugRemoting.cs +++ b/UIX/Microsoft/Iris/Debug/DebugRemoting.cs @@ -17,7 +17,7 @@ public static class DebugRemoting internal static IFormatter CreateBsonFormatter() => new BsonFormatter(new StreamingContext(StreamingContextStates.Remoting)); - internal static Data.DebuggerMessageFrame ReceiveDebuggerMessage(Socket socket) + internal static Data.DebuggerMessageFrame ReceiveDebuggerMessage(Socket socket, IFormatter formatter) { try { @@ -33,7 +33,7 @@ public static class DebugRemoting byte[] frameBytes = new byte[frameLength]; socket.Receive(frameBytes, frameLength, SocketFlags.None); - return new(frameBytes); + return new(frameBytes, formatter); } catch (SocketException) { diff --git a/UIX/Microsoft/Iris/Debug/DebugSettings.cs b/UIX/Microsoft/Iris/Debug/DebugSettings.cs index ae28aef..004d0ab 100644 --- a/UIX/Microsoft/Iris/Debug/DebugSettings.cs +++ b/UIX/Microsoft/Iris/Debug/DebugSettings.cs @@ -14,7 +14,7 @@ public class DebugSettings public bool GenerateDataMappingModels { get; set; } = false; public ObservableCollection DataMappingModels { get; } = new(); - public List Breakpoints { get; } = new(); + public HashSet Breakpoints { get; } = new(); public string DebugConnectionUri { get; set; } } diff --git a/UIX/Microsoft/Iris/Debug/DebuggerMessageType.cs b/UIX/Microsoft/Iris/Debug/DebuggerMessageType.cs index 409844e..b685e0d 100644 --- a/UIX/Microsoft/Iris/Debug/DebuggerMessageType.cs +++ b/UIX/Microsoft/Iris/Debug/DebuggerMessageType.cs @@ -10,4 +10,6 @@ public enum DebuggerMessageType : int UpdateBreakpoint, InterpreterCommand, + + LineNumberTable, } diff --git a/UIX/Microsoft/Iris/Debug/IDebuggerClient.cs b/UIX/Microsoft/Iris/Debug/IDebuggerClient.cs index 81e4e9f..3fcba82 100644 --- a/UIX/Microsoft/Iris/Debug/IDebuggerClient.cs +++ b/UIX/Microsoft/Iris/Debug/IDebuggerClient.cs @@ -23,4 +23,10 @@ public interface IDebuggerClient : IDebuggerState event Action DispatcherStep; void UpdateBreakpoint(Breakpoint breakpoint); + + /// + /// Requests the line number table for the given UIX file. + /// + /// The URI of the file to get information for. + void RequestLineNumberTable(string uri, Action callback); } diff --git a/UIX/Microsoft/Iris/Debug/IDebuggerServer.cs b/UIX/Microsoft/Iris/Debug/IDebuggerServer.cs index 312d1bb..f1aa8ea 100644 --- a/UIX/Microsoft/Iris/Debug/IDebuggerServer.cs +++ b/UIX/Microsoft/Iris/Debug/IDebuggerServer.cs @@ -4,6 +4,11 @@ namespace Microsoft.Iris.Debug; internal interface IDebuggerServer : IDebuggerState { + /// + /// Sends the requested line number table. + /// + MarkupLineNumberEntry[] OnLineNumberTableRequested(string uri); + /// /// Logs the context, opcode, and operands of an instruction /// decoded by Microsoft.Iris.Markup.Interpreter. diff --git a/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerClient.cs b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerClient.cs index 09c9647..37995a7 100644 --- a/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerClient.cs +++ b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerClient.cs @@ -11,8 +11,11 @@ public class NetDebuggerClient : IDebuggerClient, IDisposable { private readonly Socket _socket; private readonly ConcurrentQueue _outQueue = new(); + private readonly ConcurrentDictionary> _requests = new(); private readonly IFormatter _formatter; + private InterpreterCommand _uibCommand; + private long _nextFreeTransactionId = 1; public Uri ConnectionUri { get; } @@ -21,8 +24,7 @@ public class NetDebuggerClient : IDebuggerClient, IDisposable get => _uibCommand; set { - var data = new[] { (byte)value }; - QueueDebuggerMessage(new(0, DebuggerMessageType.InterpreterCommand, data)); + QueueDebuggerMessage(new(0, DebuggerMessageType.InterpreterCommand, value, _formatter)); _uibCommand = value; } } @@ -57,9 +59,16 @@ public class NetDebuggerClient : IDebuggerClient, IDisposable } } + public void RequestLineNumberTable(string uri, Action callback) + { + DebuggerMessageFrame frame = new(GetNextTransactionId(), DebuggerMessageType.LineNumberTable, uri, _formatter); + _requests.TryAdd(frame.TransactionId, o => callback((MarkupLineNumberEntry[])o)); + QueueDebuggerMessage(frame); + } + public void UpdateBreakpoint(Breakpoint breakpoint) { - QueueDebuggerMessage(new(0, DebuggerMessageType.UpdateBreakpoint, breakpoint.Serialize(_formatter))); + QueueDebuggerMessage(new(0, DebuggerMessageType.UpdateBreakpoint, breakpoint, _formatter)); } private void QueueDebuggerMessage(DebuggerMessageFrame frame) @@ -67,36 +76,55 @@ public class NetDebuggerClient : IDebuggerClient, IDisposable _outQueue.Enqueue(frame.ToBytes()); } + private long GetNextTransactionId() + { + var currentId = _nextFreeTransactionId; + + if (currentId == -1) + _nextFreeTransactionId += 2; + else if (currentId == long.MaxValue) + _nextFreeTransactionId = long.MinValue; + else + ++_nextFreeTransactionId; + + return currentId; + } + private void MessageReceiveLoop() { while (_socket.Connected) { DebuggerMessageFrame frame; - while ((frame = DebugRemoting.ReceiveDebuggerMessage(_socket)) == null) + while ((frame = DebugRemoting.ReceiveDebuggerMessage(_socket, _formatter)) == null) if (!_socket.Connected) return; switch (frame.Type) { case DebuggerMessageType.InterpreterDecode: - var decEntry = frame.DeserializeData(_formatter); + var decEntry = frame.GetValue(); InterpreterDecode?.Invoke(this, decEntry); break; - + case DebuggerMessageType.InterpreterExecute: - var execEntry = frame.DeserializeData(_formatter); + var execEntry = frame.GetValue(); InterpreterExecute?.Invoke(this, execEntry); break; case DebuggerMessageType.DispatcherStep: - string message = frame.GetDataAsString(); + string message = frame.GetValue(); DispatcherStep?.Invoke(message); break; case DebuggerMessageType.InterpreterCommand: - _uibCommand = (InterpreterCommand)frame.Data[0]; + _uibCommand = frame.GetValue(); InterpreterStateChanged?.Invoke(_uibCommand); break; + case DebuggerMessageType.LineNumberTable: + if (_requests.TryRemove(frame.TransactionId, out var callback)) + callback(frame.GetValue()); + break; + default: Trace.WriteLine(TraceCategory.MarkupDebug, "Received unknown debugger message of type '{0}'.", frame.Type); break; diff --git a/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs index c1ad4a6..6ae3658 100644 --- a/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs +++ b/UIX/Microsoft/Iris/Debug/SystemNet/NetDebuggerServer.cs @@ -1,4 +1,5 @@ using Microsoft.Iris.Debug.Data; +using Microsoft.Iris.Markup; using System; using System.Collections.Concurrent; using System.Net.Sockets; @@ -26,8 +27,7 @@ internal class NetDebuggerServer : IDebuggerServer, IDisposable get => _uibCommand; set { - var data = new[] { (byte)value }; - QueueDebuggerMessage(new(0, DebuggerMessageType.InterpreterCommand, data)); + QueueDebuggerMessage(new(0, DebuggerMessageType.InterpreterCommand, value, _formatter)); _uibCommand = value; } } @@ -51,19 +51,27 @@ internal class NetDebuggerServer : IDebuggerServer, IDisposable Current = this; } + public MarkupLineNumberEntry[] OnLineNumberTableRequested(string uri) + { + var loadResult = LoadResultCache.Read(uri) as MarkupLoadResult; + var lineNumberTable = loadResult.LineNumberTable.DumpTable(); + + return lineNumberTable; + } + public void LogInterpreterDecode(object context, InterpreterInstruction instruction) { - QueueDebuggerMessage(new(0, DebuggerMessageType.InterpreterDecode, instruction.Serialize(_formatter))); + QueueDebuggerMessage(new(0, DebuggerMessageType.InterpreterDecode, instruction, _formatter)); } public void LogInterpreterExecute(object context, InterpreterEntry entry) { - QueueDebuggerMessage(new(0, DebuggerMessageType.InterpreterExecute, entry.Serialize(_formatter))); + QueueDebuggerMessage(new(0, DebuggerMessageType.InterpreterExecute, entry, _formatter)); } public void LogDispatcher(string message) { - QueueDebuggerMessage(new(0, DebuggerMessageType.DispatcherStep, message)); + QueueDebuggerMessage(new(0, DebuggerMessageType.DispatcherStep, message, _formatter)); } public void Dispose() @@ -86,13 +94,13 @@ internal class NetDebuggerServer : IDebuggerServer, IDisposable while (_socket.Connected) { DebuggerMessageFrame frame; - while ((frame = DebugRemoting.ReceiveDebuggerMessage(_socket)) == null) + while ((frame = DebugRemoting.ReceiveDebuggerMessage(_socket, _formatter)) == null) if (!_socket.Connected) return; switch (frame.Type) { case DebuggerMessageType.UpdateBreakpoint: - var breakpoint = frame.DeserializeData(_formatter); + var breakpoint = frame.GetValue(); if (breakpoint.Enabled) Application.DebugSettings.Breakpoints.Add(breakpoint); else @@ -100,7 +108,16 @@ internal class NetDebuggerServer : IDebuggerServer, IDisposable break; case DebuggerMessageType.InterpreterCommand: - _uibCommand = (InterpreterCommand)frame.Data[0]; + _uibCommand = frame.GetValue(); + break; + + case DebuggerMessageType.LineNumberTable: + var lineNumberTable = OnLineNumberTableRequested(frame.GetValue()); + + DebuggerMessageFrame responseFrame = + new(frame.TransactionId, DebuggerMessageType.LineNumberTable, lineNumberTable, _formatter); + + QueueDebuggerMessage(responseFrame); break; default: diff --git a/UIX/Microsoft/Iris/Markup/MarkupLineNumberTable.cs b/UIX/Microsoft/Iris/Markup/MarkupLineNumberTable.cs index b54f32d..92ab0e8 100644 --- a/UIX/Microsoft/Iris/Markup/MarkupLineNumberTable.cs +++ b/UIX/Microsoft/Iris/Markup/MarkupLineNumberTable.cs @@ -62,14 +62,14 @@ namespace Microsoft.Iris.Markup internal ulong[] PersistList => _runtimeList; - public Vector DumpTable() + public Debug.Data.MarkupLineNumberEntry[] DumpTable() { - Vector knownLines = new(); + var knownLines = new Debug.Data.MarkupLineNumberEntry[_runtimeList.Length]; for (int index = 0; index < _runtimeList.Length; ++index) { var value = _runtimeList[index]; - knownLines.Add(new(UnpackOffset(value), UnpackLine(value), UnpackColumn(value))); + knownLines[index] = new(UnpackOffset(value), UnpackLine(value), UnpackColumn(value)); } return knownLines;