From 0921c7085bca235b2193c4a51e077237a9efe1af Mon Sep 17 00:00:00 2001 From: Gregg Miskelly Date: Tue, 30 Jun 2026 13:12:32 -0700 Subject: [PATCH 1/4] Enable nullable reference types in MICore (#1603) - Add enable to MICore.csproj - Add GlobalUsings.cs with NullableHelpers static import - Add NullableAttributes.cs shared file - Add nullable annotations (?) to all appropriate type declarations - Add [NotNullWhen], [DoesNotReturn] attributes - Add Debug.Assert statements for non-null invariants - Replace string.IsNullOrEmpty/IsNullOrWhiteSpace with NullableHelpers versions - Add using System.Diagnostics where needed for Debug class - Remove redundant using aliases replaced by GlobalUsings This also cleans up the code in `PipeTransport.ExecuteSyncCommand` as that code had the classic Process.Start deadlock --- docs/CodingStandards-CSharp-for-AI.md | 14 + src/MICore/Checksum.cs | 4 +- .../CommandFactories/MICommandFactory.cs | 15 +- src/MICore/CommandFactories/gdb.cs | 11 +- src/MICore/CommandFactories/lldb.cs | 9 +- src/MICore/CommandLock.cs | 21 +- src/MICore/Debugger.cs | 101 +++-- src/MICore/DebuggerDisposedException.cs | 6 +- src/MICore/ExceptionHelper.cs | 5 +- src/MICore/GlobalUsings.cs | 5 + src/MICore/IncludeExcludeList.cs | 10 +- src/MICore/JsonLaunchOptions.cs | 193 ++++----- src/MICore/LaunchCommand.cs | 19 +- src/MICore/LaunchOptions.cs | 384 ++++++++++-------- src/MICore/Logger.cs | 13 +- src/MICore/MICore.csproj | 4 + src/MICore/MIException.cs | 6 +- src/MICore/MIResults.cs | 234 +++++------ src/MICore/PlatformUtilities.cs | 8 +- src/MICore/ProcessMonitor.cs | 10 +- src/MICore/RunInTerminalLauncher.cs | 9 +- .../Transports/ClientServerTransport.cs | 3 +- src/MICore/Transports/ITransport.cs | 4 +- src/MICore/Transports/LocalTransport.cs | 9 +- src/MICore/Transports/MockTransport.cs | 25 +- src/MICore/Transports/PipeTransport.cs | 71 ++-- .../Transports/RunInTerminalTransport.cs | 50 +-- src/MICore/Transports/ServerTransport.cs | 11 +- src/MICore/Transports/StreamTransport.cs | 45 +- src/MICore/Transports/TcpTransport.cs | 8 +- .../Transports/UnixShellPortTransport.cs | 31 +- src/MICore/UnixUtilities.cs | 9 +- src/MICore/Utilities.cs | 4 +- 33 files changed, 721 insertions(+), 630 deletions(-) create mode 100644 src/MICore/GlobalUsings.cs diff --git a/docs/CodingStandards-CSharp-for-AI.md b/docs/CodingStandards-CSharp-for-AI.md index 9791fd020..66611555f 100644 --- a/docs/CodingStandards-CSharp-for-AI.md +++ b/docs/CodingStandards-CSharp-for-AI.md @@ -40,3 +40,17 @@ These aren't in `.editorconfig` but show up everywhere — follow the existing c - **Host calls go through `DebugEngineHost`.** MIDebugEngine never references `Microsoft.VisualStudio.*` directly; use `HostLogger`, `HostMarshal`, `HostOutputWindow`, etc. - **AD7 surface lives on `AD7*` partial classes.** Keep VS-SDK COM concerns out of the core `Debugged*` classes. - **Worker thread discipline.** AD7 callbacks must not block. Use `Task.Run` for work and post results back via the engine's `WorkerThread` / `EngineCallback`. Mirror existing call sites; do not invent new threading patterns. + +## Nullable reference types and `Debug` + +Projects with nullable reference types enabled **must never** use `System.Diagnostics.Debug` directly. Do **not** add `using System.Diagnostics;` or `using Debug = System.Diagnostics.Debug;` in any file in a nullable-enabled project. + +Instead, use `Microsoft.DebugEngineHost.NullableHelpers.Debug`, which is a wrapper that adds `[DoesNotReturn]` / `[DoesNotReturnIf(false)]` attributes so the C# nullable flow analyser understands that `Debug.Assert`/`Debug.Fail` stop execution. This wrapper is brought into scope via the file `GlobalUsings.cs` in each nullable-enabled project: + +```csharp +global using static global::Microsoft.DebugEngineHost.NullableHelpers; +``` + +With that global using in place every `Debug.Assert(...)` and `Debug.Fail(...)` call in the project automatically resolves to `NullableHelpers.Debug` — no per-file `using` is needed. **Never shadow this with a per-file alias or a `using System.Diagnostics;` import.** + +The same global using also brings `IsNullOrEmpty` and `IsNullOrWhiteSpace` into scope as drop-in replacements for `string.IsNullOrEmpty`/`string.IsNullOrWhiteSpace` with the proper `[NotNullWhen(false)]` annotation. diff --git a/src/MICore/Checksum.cs b/src/MICore/Checksum.cs index 0421be385..c98a02328 100644 --- a/src/MICore/Checksum.cs +++ b/src/MICore/Checksum.cs @@ -22,8 +22,8 @@ public enum MIHashAlgorithmName public class Checksum { - private string _checksumString = null; - private byte[] _bytes = null; + private string? _checksumString = null; + private byte[] _bytes; public readonly MIHashAlgorithmName MIHashAlgorithmName; diff --git a/src/MICore/CommandFactories/MICommandFactory.cs b/src/MICore/CommandFactories/MICommandFactory.cs index a5d0f01ce..54923e820 100644 --- a/src/MICore/CommandFactories/MICommandFactory.cs +++ b/src/MICore/CommandFactories/MICommandFactory.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Threading.Tasks; using System.IO; using System.Text; @@ -81,7 +80,7 @@ public static MICommandFactory GetInstance(MIMode mode, Debugger debugger) return commandFactory; } - public static string SpanNextAddr(string line, out ulong addr) + public static string? SpanNextAddr(string line, out ulong addr) { addr = 0; char[] endOfNum = { ' ', '\t', '\"' }; @@ -483,7 +482,7 @@ internal bool PreparePath(string path, bool useUnixFormat, out string pathMI) return requiresQuotes; } - public virtual async Task BreakInsert(string filename, bool useUnixFormat, uint line, string condition, bool enabled, IEnumerable checksums = null, ResultClass resultClass = ResultClass.done) + public virtual async Task BreakInsert(string filename, bool useUnixFormat, uint line, string condition, bool enabled, IEnumerable? checksums = null, ResultClass resultClass = ResultClass.done) { StringBuilder cmd = await BuildBreakInsert(condition, enabled); @@ -533,7 +532,7 @@ public virtual Task BreakWatch(string address, uint size, ResultClass r public virtual bool SupportsDataBreakpoints { get { return false; } } - public virtual async Task BreakInfo(string bkptno) + public virtual async Task BreakInfo(string bkptno) { Results bindResult = await _debugger.CmdAsync("-break-info " + bkptno, ResultClass.None); if (bindResult.ResultClass != ResultClass.done) @@ -563,7 +562,7 @@ public virtual async Task BreakDelete(string bkptno, ResultClass resultClass = R public virtual async Task BreakCondition(string bkptno, string expr) { - if (string.IsNullOrWhiteSpace(expr)) + if (IsNullOrWhiteSpace(expr)) { expr = string.Empty; } @@ -632,7 +631,7 @@ public virtual void DecodeExceptionReceivedProperties(Results miExceptionResult, #region Miscellaneous - public virtual Task AutoComplete(string command, int threadId, uint frameLevel) + public virtual Task AutoComplete(string command, int threadId, uint frameLevel) { throw new NotImplementedException(); } @@ -708,9 +707,9 @@ public virtual AsyncBreakSignal GetAsyncBreakSignal(Results results) return MICore.AsyncBreakSignal.None; } - public Results IsModuleLoad(string cmd) + public Results? IsModuleLoad(string cmd) { - Results results = null; + Results? results = null; if (cmd.StartsWith("library-loaded,", StringComparison.Ordinal)) { MIResults res = new MIResults(_debugger.Logger); diff --git a/src/MICore/CommandFactories/gdb.cs b/src/MICore/CommandFactories/gdb.cs index 60954d46a..a268df1c2 100644 --- a/src/MICore/CommandFactories/gdb.cs +++ b/src/MICore/CommandFactories/gdb.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Threading.Tasks; using System.IO; using System.Text; @@ -57,7 +56,7 @@ protected override async Task ThreadFrameCmdAsync(string command, strin { // first aquire an exclusive lock. This is used as we don't want to fight with other commands that also require the current // thread to be set to a particular value - ExclusiveLockToken lockToken = await _debugger.CommandLock.AquireExclusive(); + ExclusiveLockToken? lockToken = await _debugger.CommandLock.AquireExclusive(); try { @@ -103,7 +102,7 @@ protected override async Task ThreadCmdAsync(string command, string arg { // first aquire an exclusive lock. This is used as we don't want to fight with other commands that also require the current // thread to be set to a particular value - ExclusiveLockToken lockToken = await _debugger.CommandLock.AquireExclusive(); + ExclusiveLockToken? lockToken = await _debugger.CommandLock.AquireExclusive(); try { @@ -193,7 +192,7 @@ public override async Task> StartAddressesForLine(string file, uint { while (true) { - string resultLine = stringReader.ReadLine(); + string? resultLine = stringReader.ReadLine(); if (resultLine == null) break; @@ -281,7 +280,7 @@ public override TargetArchitecture ParseTargetArchitectureResult(string result) { while (true) { - string resultLine = stringReader.ReadLine(); + string? resultLine = stringReader.ReadLine(); if (resultLine == null) break; @@ -327,7 +326,7 @@ public override async Task Catch(string name, bool onlyOnce = false, ResultClass await _debugger.ConsoleCmdAsync(command + name, allowWhileRunning: false); } - public override async Task AutoComplete(string command, int threadId, uint frameLevel) + public override async Task AutoComplete(string command, int threadId, uint frameLevel) { string cmd = "-complete"; string args = $"\"{command}\""; diff --git a/src/MICore/CommandFactories/lldb.cs b/src/MICore/CommandFactories/lldb.cs index f423c2fc0..ffcb609b3 100644 --- a/src/MICore/CommandFactories/lldb.cs +++ b/src/MICore/CommandFactories/lldb.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Threading.Tasks; using System.IO; using System.Text; @@ -116,13 +115,13 @@ protected override async Task ThreadCmdAsync(string command, string arg public override Task> StartAddressesForLine(string file, uint line) { - return Task.FromResult>(null); + return Task.FromResult(new List()); } public override Task EnableTargetAsyncOption() { // lldb-mi doesn't support target-async mode, and doesn't seem to need to - return Task.FromResult((object)null); + return Task.CompletedTask; } public override string GetTargetArchitectureCommand() @@ -136,7 +135,7 @@ public override TargetArchitecture ParseTargetArchitectureResult(string result) { while (true) { - string resultLine = stringReader.ReadLine(); + string? resultLine = stringReader.ReadLine(); if (resultLine == null) break; @@ -216,7 +215,7 @@ private async Task RequiresOnKeywordForBreakInsert() { // Query for the version. string version = await Version(); - if (!string.IsNullOrWhiteSpace(version) && version.Trim().Equals(OldLLDBMIVersionString, StringComparison.Ordinal)) + if (!IsNullOrWhiteSpace(version) && version.Trim().Equals(OldLLDBMIVersionString, StringComparison.Ordinal)) { _requiresOnKeywordForBreakInsert = true; } diff --git a/src/MICore/CommandLock.cs b/src/MICore/CommandLock.cs index 3538ceff9..64b817fb4 100644 --- a/src/MICore/CommandLock.cs +++ b/src/MICore/CommandLock.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; @@ -18,7 +17,7 @@ sealed public class ExclusiveLockToken : IDisposable // NOTE: I tried to make this a value object, but value objects don't work quite as expected in async methods and calling 'Close' // wasn't updating the backing value object which was stored in the state machine class { - private CommandLock _commandLock; + private CommandLock? _commandLock; private int _value; internal ExclusiveLockToken(CommandLock commandLock, int value) @@ -39,7 +38,7 @@ public static bool IsNullOrClosed(ExclusiveLockToken token) return (token == null || token._value == 0); } - public override bool Equals(object obj) + public override bool Equals(object? obj) { throw new NotImplementedException(); // this method should never be called } @@ -66,6 +65,7 @@ public void ConvertToSharedLock() _value = 0; _commandLock = null; + Debug.Assert(commandLock is not null, "Should be impossible. A non-zero _value implies _commandLock is set."); commandLock.ConvertExclusiveLockToShared(value); } @@ -78,6 +78,7 @@ public void Close() _value = 0; _commandLock = null; + Debug.Assert(commandLock is not null, "Should be impossible. A non-zero _value implies _commandLock is set."); commandLock.ReleaseExclusive(value); } } @@ -99,9 +100,9 @@ sealed public class CommandLock private int _prevExclusiveToken; private int _pendingSharedLockRequests; - private TaskCompletionSource _waitingSharedLockSource; + private TaskCompletionSource? _waitingSharedLockSource; private readonly Queue> _waitingExclusiveLockRequests = new Queue>(); - private string _closeMessage; + private string _closeMessage = string.Empty; public CommandLock() { @@ -184,7 +185,7 @@ public Task AquireShared() // Internal method called from the ExclusiveLockToken class as part of closing an exclusive lock internal void ReleaseExclusive(int tokenValue) { - Action actionAfterReleaseLock = null; + Action? actionAfterReleaseLock = null; lock (this.LockObject) { @@ -212,7 +213,7 @@ internal void ReleaseExclusive(int tokenValue) // Internal method called from the ExclusiveLockToken class to convert an exclusive lock into a shared lock internal void ConvertExclusiveLockToShared(int tokenValue) { - Action actionAfterReleaseLock = null; + Action? actionAfterReleaseLock = null; lock (this.LockObject) { @@ -240,7 +241,7 @@ internal void ConvertExclusiveLockToShared(int tokenValue) public void ReleaseShared() { - Action actionAfterReleaseLock = null; + Action? actionAfterReleaseLock = null; lock (this.LockObject) { @@ -269,7 +270,7 @@ public void ReleaseShared() } // NOTE: This method MUST be called with this.LockObject held - private Action GetAfterReleaseLockAction() + private Action? GetAfterReleaseLockAction() { Debug.Assert(_lockStatus == StatusFree, "Why is GetAfterReleaseLockAction called when the lock is not free?"); @@ -298,7 +299,7 @@ private ExclusiveLockToken GetNextExclusiveLockToken() } // NOTE: This method MUST be called with this.LockObject held - private Action MaybeSignalPendingSharedLockRequests() + private Action? MaybeSignalPendingSharedLockRequests() { Debug.Assert(_lockStatus >= 0, "Why is MaybeGetSharedLockAction called when the lock is not free/reading?"); diff --git a/src/MICore/Debugger.cs b/src/MICore/Debugger.cs index b411cd086..e77fd8e76 100755 --- a/src/MICore/Debugger.cs +++ b/src/MICore/Debugger.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Threading; using System.Text; -using System.Diagnostics; using System.Threading.Tasks; using System.Globalization; using System.Linq; @@ -29,22 +28,22 @@ public class Debugger : ITransportCallback private const string Event_UnsupportedWindowsGdb = "VS/Diagnostics/Debugger/MIEngine/UnsupportedWindowsGdb"; private const string Property_GdbVersion = "VS.Diagnostics.Debugger.MIEngine.GdbVersion"; - public event EventHandler BreakModeEvent; - public event EventHandler RunModeEvent; - public event EventHandler ProcessExitEvent; - public event EventHandler DebuggerExitEvent; - public event EventHandler DebuggerAbortedEvent; - public event EventHandler OutputStringEvent; - public event EventHandler EvaluationEvent; - public event EventHandler ErrorEvent; - public event EventHandler ModuleLoadEvent; // occurs when stopped after a libraryLoadEvent - public event EventHandler LibraryLoadEvent; // a shared library was loaded - public event EventHandler BreakChangeEvent; // a breakpoint was changed - public event EventHandler BreakCreatedEvent; // a breakpoint was created - public event EventHandler ThreadCreatedEvent; - public event EventHandler ThreadExitedEvent; - public event EventHandler ThreadGroupExitedEvent; - public event EventHandler TelemetryEvent; + public event EventHandler? BreakModeEvent; + public event EventHandler? RunModeEvent; + public event EventHandler? ProcessExitEvent; + public event EventHandler? DebuggerExitEvent; + public event EventHandler? DebuggerAbortedEvent; + public event EventHandler? OutputStringEvent; + public event EventHandler? EvaluationEvent; + public event EventHandler? ErrorEvent; + public event EventHandler? ModuleLoadEvent; // occurs when stopped after a libraryLoadEvent + public event EventHandler? LibraryLoadEvent; // a shared library was loaded + public event EventHandler? BreakChangeEvent; // a breakpoint was changed + public event EventHandler? BreakCreatedEvent; // a breakpoint was created + public event EventHandler? ThreadCreatedEvent; + public event EventHandler? ThreadExitedEvent; + public event EventHandler? ThreadGroupExitedEvent; + public event EventHandler? TelemetryEvent; private int _exiting; public ProcessState ProcessState { get; private set; } private MIResults _miResults; @@ -80,10 +79,10 @@ public bool IsClosed public LaunchOptions LaunchOptions { get { return this._launchOptions; } } private Queue> _internalBreakActions = new Queue>(); - private TaskCompletionSource _internalBreakActionCompletionSource; - private TaskCompletionSource _consoleDebuggerInitializeCompletionSource = new TaskCompletionSource(); - private LinkedList _initializationLog = new LinkedList(); - private LinkedList _initialErrors = new LinkedList(); + private TaskCompletionSource? _internalBreakActionCompletionSource; + private TaskCompletionSource? _consoleDebuggerInitializeCompletionSource = new TaskCompletionSource(); + private LinkedList? _initializationLog = new LinkedList(); + private LinkedList? _initialErrors = new LinkedList(); private int _localDebuggerPid = -1; protected bool _connected; @@ -123,7 +122,7 @@ public StoppingEventArgs(Results results, BreakRequest asyncRequest = BreakReque { } } - private ITransport _transport; + private ITransport? _transport; private readonly CommandLock _commandLock = new CommandLock(); /// @@ -136,12 +135,12 @@ public StoppingEventArgs(Results results, BreakRequest asyncRequest = BreakReque /// Message used in any DebuggerDisposedExceptions once the debugger is closed. Setting /// this to non-null indicates that the debugger is now closed. It is only set once. /// - private string _closeMessage; + private string? _closeMessage; /// - /// [Optional] If a console command is being executed, list where we append the output + /// If a console command is being executed, list where we append the output /// - private StringBuilder _consoleCommandOutput; + private StringBuilder? _consoleCommandOutput; private bool _pendingInternalBreak; internal bool IsRequestingInternalAsyncBreak @@ -153,7 +152,7 @@ internal bool IsRequestingInternalAsyncBreak } private bool _waitingToStop; - private Timer _breakTimer = null; + private Timer? _breakTimer = null; private int _retryCount; private const int BREAK_DELTA = 3000; // millisec before trying to break again private const int BREAK_RETRY_MAX = 3; // maximum times to retry @@ -198,7 +197,7 @@ private bool IsUnixDebuggerRunning() return false; } - private void RetryBreak(object o) + private void RetryBreak(object? o) { lock (_internalBreakActions) { @@ -231,7 +230,7 @@ public Task AddInternalBreakAction(Func func) { if (_internalBreakActionCompletionSource == null) { - _internalBreakActionCompletionSource = new TaskCompletionSource(); + _internalBreakActionCompletionSource = new TaskCompletionSource(); } _internalBreakActions.Enqueue(func); @@ -369,9 +368,9 @@ protected void OnStateChanged(string mode, Results results) /// Returns true if the process is continued and we should not enter break state, returns false if the process is stopped and we should enter break state. private async Task DoInternalBreakActions(bool fIsAsyncBreak) { - TaskCompletionSource source = null; - Func item = null; - Exception firstException = null; + TaskCompletionSource? source = null; + Func? item = null; + Exception? firstException = null; while (true) { lock (_internalBreakActions) @@ -431,7 +430,7 @@ private async Task DoInternalBreakActions(bool fIsAsyncBreak) return processContinued; } - public void Init(ITransport transport, LaunchOptions options, HostWaitLoop waitLoop = null) + public void Init(ITransport transport, LaunchOptions options, HostWaitLoop? waitLoop = null) { _lastCommandId = 1000; _transport = transport; @@ -587,8 +586,8 @@ public Task CmdBreak(BreakRequest request) protected bool IsLocalLaunchUsingServer() { return (_launchOptions is LocalLaunchOptions localLaunchOptions && - (!String.IsNullOrWhiteSpace(localLaunchOptions.MIDebuggerServerAddress) || - !String.IsNullOrWhiteSpace(localLaunchOptions.DebugServer))); + (!IsNullOrWhiteSpace(localLaunchOptions.MIDebuggerServerAddress) || + !IsNullOrWhiteSpace(localLaunchOptions.DebugServer))); } internal bool IsLocalGdbTarget() @@ -941,7 +940,7 @@ void ITransportCallback.OnStdOutLine(string line) if (_initializationLog != null) { _initializationLog.AddLast(line); - if (string.IsNullOrEmpty(_gdbVersion)) + if (IsNullOrEmpty(_gdbVersion)) { TryInitializeGdbVersion(line); } @@ -973,7 +972,7 @@ void ITransportCallback.OnStdErrorLine(string line) } } - public void OnDebuggerProcessExit(/*OPTIONAL*/ string exitCode) + public void OnDebuggerProcessExit(string? exitCode) { // GDB has exited. Cleanup. Only let one thread perform the cleanup if (Interlocked.CompareExchange(ref _exiting, 1, 0) == 0) @@ -1012,7 +1011,7 @@ public void OnDebuggerProcessExit(/*OPTIONAL*/ string exitCode) string message; - if (string.IsNullOrEmpty(exitCode)) + if (IsNullOrEmpty(exitCode)) message = string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_MIDebuggerExited_UnknownCode, this.MICommandFactory.Name); else message = string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_MIDebuggerExited_WithCode, this.MICommandFactory.Name, exitCode); @@ -1050,7 +1049,7 @@ void TryInitializeGdbVersion(string line) } int majorVersion = 0; - if (!string.IsNullOrWhiteSpace(_gdbVersion)) + if (!IsNullOrWhiteSpace(_gdbVersion)) { int.TryParse(_gdbVersion.Split('.').FirstOrDefault(), out majorVersion); } @@ -1111,7 +1110,7 @@ protected virtual void ScheduleResultProcessing(Action func) // a Token is a sequence of decimal digits followed by something else // returns null if not a token, or not followed by something else - private string ParseToken(ref string cmd) + private string? ParseToken(ref string cmd) { if (char.IsDigit(cmd, 0)) { @@ -1157,7 +1156,7 @@ internal void OnComplete(Results results, MICommandFactory commandFactory) { if (_expectedResultClass != ResultClass.None && _expectedResultClass != results.ResultClass) { - string miError = null; + string miError; if (results.ResultClass == ResultClass.error) { // Fixes: https://github.com/microsoft/vscode-cpptools/issues/2492 @@ -1241,7 +1240,7 @@ public void ProcessStdOutLine(string line) } else { - string token = ParseToken(ref line); + string? token = ParseToken(ref line); char c = line[0]; string noprefix = line.Substring(1).Trim(); @@ -1251,7 +1250,7 @@ public void ProcessStdOutLine(string line) if (c == '^') { uint id = uint.Parse(token, CultureInfo.InvariantCulture); - WaitingOperationDescriptor waitingOperation = null; + WaitingOperationDescriptor? waitingOperation = null; lock (_waitingOperations) { if (_waitingOperations.TryGetValue(id, out waitingOperation)) @@ -1273,7 +1272,7 @@ public void ProcessStdOutLine(string line) uint id = uint.Parse(token, CultureInfo.InvariantCulture); lock (_waitingOperations) { - WaitingOperationDescriptor waitingOperation; + WaitingOperationDescriptor? waitingOperation; if (_waitingOperations.TryGetValue(id, out waitingOperation) && line == waitingOperation.Command) { @@ -1317,7 +1316,7 @@ private void OnUnknown(string cmd) Debug.WriteLine("DBG:Unknown command: {0}", cmd); } - private void OnResult(string cmd, string token) + private void OnResult(string cmd, string? token) { uint id = token != null ? uint.Parse(token, CultureInfo.InvariantCulture) : 0; Results results = _miResults.ParseCommandOutput(cmd); @@ -1494,7 +1493,7 @@ private void OnNotificationOutput(string cmd) public string GetLastSentCommandName() { string lastCommandText = _lastCommandText; - if (string.IsNullOrEmpty(lastCommandText)) + if (IsNullOrEmpty(lastCommandText)) { // We haven't sent any commands yet return string.Empty; @@ -1571,7 +1570,7 @@ private void HandleThreadGroupExited(Results results) string threadGroupId = results.TryFindString("id"); bool isThreadGroupEmpty = false; - if (!String.IsNullOrEmpty(threadGroupId)) + if (!IsNullOrEmpty(threadGroupId)) { lock (_debuggeePids) { @@ -1619,7 +1618,7 @@ private async void PostCommand(string cmd) private void SendToTransport(string cmd) { - ITransport transport = _transport; + ITransport? transport = _transport; if (transport is null) { Debug.Fail("Invalid: `SendToTransport` called before `Init`"); @@ -1640,7 +1639,7 @@ private void SendToTransport(string cmd) public static ulong ParseAddr(string addr, bool throwOnError = false) { ulong res = 0; - if (string.IsNullOrEmpty(addr)) + if (IsNullOrEmpty(addr)) { if (throwOnError) { @@ -1676,7 +1675,7 @@ public static ulong ParseAddr(string addr, bool throwOnError = false) public static uint ParseUint(string str, bool throwOnError = false) { uint value = 0; - if (string.IsNullOrEmpty(str)) + if (IsNullOrEmpty(str)) { if (throwOnError) { @@ -1719,9 +1718,9 @@ public void VerifyNotDebuggingCoreDump() public class DebuggerAbortedEventArgs { public readonly string Message; - public readonly string /*OPTIONAL*/ ExitCode; + public readonly string? ExitCode; - public DebuggerAbortedEventArgs(string message, string exitCode) + public DebuggerAbortedEventArgs(string message, string? exitCode) { Debug.Assert(message != null, "Invalid argument"); this.Message = message; diff --git a/src/MICore/DebuggerDisposedException.cs b/src/MICore/DebuggerDisposedException.cs index 19d5c3547..01b0aa71e 100644 --- a/src/MICore/DebuggerDisposedException.cs +++ b/src/MICore/DebuggerDisposedException.cs @@ -13,12 +13,12 @@ public class DebuggerDisposedException : ObjectDisposedException /// /// Command where the abort happened. /// - public string AbortedCommand { get; private set; } + public string? AbortedCommand { get; private set; } /// /// Constructor for the DebuggerDisposedException which takes message, innerException and aborted command. /// - public DebuggerDisposedException(string message, Exception innerException, string abortedCommand = null) : base(message, innerException) + public DebuggerDisposedException(string message, Exception? innerException, string? abortedCommand = null) : base(message, innerException) { AbortedCommand = abortedCommand; } @@ -26,7 +26,7 @@ public DebuggerDisposedException(string message, Exception innerException, strin /// /// Constructor for the DebuggerDisposedException which takes message and aborted command. /// - public DebuggerDisposedException(string message, string abortedCommand = null) : this(message, null, abortedCommand) + public DebuggerDisposedException(string message, string? abortedCommand = null) : this(message, null, abortedCommand) { } } diff --git a/src/MICore/ExceptionHelper.cs b/src/MICore/ExceptionHelper.cs index 7295142b8..65d24187c 100644 --- a/src/MICore/ExceptionHelper.cs +++ b/src/MICore/ExceptionHelper.cs @@ -4,7 +4,6 @@ using Microsoft.DebugEngineHost; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.Linq; using System.Text; @@ -21,7 +20,7 @@ public static class ExceptionHelper /// For logging messages /// If true, only corrupting exceptions are reported /// true - public static bool BeforeCatch(Exception currentException, Logger logger, bool reportOnlyCorrupting) + public static bool BeforeCatch(Exception currentException, Logger? logger, bool reportOnlyCorrupting) { if (reportOnlyCorrupting && !IsCorruptingException(currentException)) { @@ -33,7 +32,7 @@ public static bool BeforeCatch(Exception currentException, Logger logger, bool r HostTelemetry.ReportCurrentException(currentException, "Microsoft.MIDebugEngine"); logger?.WriteLine(LogLevel.Error, "EXCEPTION: ", currentException.GetType()); - logger?.WriteTextBlock(LogLevel.Error, "EXCEPTION: ", currentException.StackTrace); + logger?.WriteTextBlock(LogLevel.Error, "EXCEPTION: ", currentException.StackTrace ?? string.Empty); } catch { diff --git a/src/MICore/GlobalUsings.cs b/src/MICore/GlobalUsings.cs new file mode 100644 index 000000000..dc4ca9822 --- /dev/null +++ b/src/MICore/GlobalUsings.cs @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +global using static global::Microsoft.DebugEngineHost.NullableHelpers; +global using Process = global::System.Diagnostics.Process; diff --git a/src/MICore/IncludeExcludeList.cs b/src/MICore/IncludeExcludeList.cs index 78ade62d7..ceeec2c7f 100644 --- a/src/MICore/IncludeExcludeList.cs +++ b/src/MICore/IncludeExcludeList.cs @@ -12,8 +12,8 @@ namespace MICore.SymbolLocator public class IncludeExcludeList { static readonly char[] WildCardCharacters = new char[] { '*', '?' }; - Lazy> _wildcardEntries; - Lazy> _qualifiedEntries; + Lazy> _wildcardEntries = new Lazy>(() => new List()); + Lazy> _qualifiedEntries = new Lazy>(() => new HashSet(StringComparer.Ordinal)); public bool IsEmpty { @@ -31,7 +31,7 @@ public IncludeExcludeList() public void Add(string entry) { - if (string.IsNullOrEmpty(entry)) + if (IsNullOrEmpty(entry)) return; if (entry.IndexOfAny(WildCardCharacters) >= 0) @@ -92,12 +92,12 @@ public bool Contains(string moduleName) public void Clear() { - if (_wildcardEntries == null || _wildcardEntries.IsValueCreated) + if (_wildcardEntries.IsValueCreated) { _wildcardEntries = new Lazy>(() => new List()); } - if (_qualifiedEntries == null || _qualifiedEntries.IsValueCreated) + if (_qualifiedEntries.IsValueCreated) { _qualifiedEntries = new Lazy>(() => new HashSet(StringComparer.Ordinal)); } diff --git a/src/MICore/JsonLaunchOptions.cs b/src/MICore/JsonLaunchOptions.cs index f34021960..06b64d771 100644 --- a/src/MICore/JsonLaunchOptions.cs +++ b/src/MICore/JsonLaunchOptions.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.Runtime.Serialization; using Newtonsoft.Json; @@ -18,32 +17,32 @@ public abstract partial class BaseOptions /// Semicolon separated list of directories to use to search for .so files. Example: "c:\dir1;c:\dir2". /// [JsonProperty("additionalSOLibSearchPath", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string AdditionalSOLibSearchPath { get; set; } + public string? AdditionalSOLibSearchPath { get; set; } /// /// Full path to program executable. /// [JsonProperty("program")] - public string Program { get; set; } + public string? Program { get; set; } /// /// The type of the engine. Must be "cppdbg". /// [JsonProperty("type", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string Type { get; set; } + public string? Type { get; set; } /// /// The architecture of the debuggee. This will automatically be detected unless this parameter is set. Allowed values are x86, arm, arm64, mips, x64, amd64, x86_64. /// [JsonProperty("targetArchitecture", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string TargetArchitecture { get; set; } + public string? TargetArchitecture { get; set; } /// /// .natvis files to be used when debugging this process. This option is not compatible with GDB pretty printing. Please also see "showDisplayString" if using this setting. /// [JsonProperty("visualizerFile", DefaultValueHandling = DefaultValueHandling.Ignore)] [JsonConverter(typeof(VisualizerFileConverter))] - public List VisualizerFile { get; set; } + public List? VisualizerFile { get; set; } /// /// When a visualizerFile is specified, showDisplayString will enable the display string. Turning this option on can cause slower performance during debugging. @@ -55,25 +54,25 @@ public abstract partial class BaseOptions /// Indicates the console debugger that the MIDebugEngine will connect to. Allowed values are "gdb" "lldb". /// [JsonProperty(nameof(MIMode), DefaultValueHandling = DefaultValueHandling.Ignore)] - public string MIMode { get; set; } + public string? MIMode { get; set; } /// /// The path to the mi debugger (such as gdb). When unspecified, it will search path first for the debugger. /// [JsonProperty("miDebuggerPath", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string MiDebuggerPath { get; set; } + public string? MiDebuggerPath { get; set; } /// /// Arguments for the mi debugger. /// [JsonProperty("miDebuggerArgs", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string MiDebuggerArgs { get; set; } + public string? MiDebuggerArgs { get; set; } /// /// Network address of the MI Debugger Server to connect to (example: localhost:1234). /// [JsonProperty("miDebuggerServerAddress", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string MiDebuggerServerAddress { get; set; } + public string? MiDebuggerServerAddress { get; set; } /// /// If true, use gdb extended-remote mode to connect to gdbserver. @@ -85,37 +84,37 @@ public abstract partial class BaseOptions /// Optional source file mappings passed to the debug engine. Example: '{ "/original/source/path":"/current/source/path" }' /// [JsonProperty("sourceFileMap", DefaultValueHandling = DefaultValueHandling.Ignore)] - public Dictionary SourceFileMap { get; protected set; } + public Dictionary? SourceFileMap { get; protected set; } /// /// When present, this tells the debugger to connect to a remote computer using another executable as a pipe that will relay standard input/output between VS Code and the MI-enabled debugger backend executable (such as gdb). /// [JsonProperty("pipeTransport", DefaultValueHandling = DefaultValueHandling.Ignore)] - public PipeTransport PipeTransport { get; set; } + public PipeTransport? PipeTransport { get; set; } /// /// Supports explcit control of symbol loading. The processing of Exceptions lists and symserver entries. /// [JsonProperty("symbolLoadInfo", DefaultValueHandling = DefaultValueHandling.Ignore)] - public SymbolLoadInfo SymbolLoadInfo { get; set; } + public SymbolLoadInfo? SymbolLoadInfo { get; set; } /// /// One or more GDB/LLDB commands to execute in order to setup the underlying debugger. Example: "setupCommands": [ { "text": "-enable-pretty-printing", "description": "Enable GDB pretty printing", "ignoreFailures": true }]. /// [JsonProperty("setupCommands", DefaultValueHandling = DefaultValueHandling.Ignore)] - public List SetupCommands { get; protected set; } + public List? SetupCommands { get; protected set; } /// /// One or more commands to execute in order to setup underlying debugger after debugger has been attached. i.e. flashing and resetting the board /// [JsonProperty("postRemoteConnectCommands", DefaultValueHandling = DefaultValueHandling.Ignore)] - public List PostRemoteConnectCommands { get; protected set; } + public List? PostRemoteConnectCommands { get; protected set; } /// /// Explicitly control whether hardware breakpoints are used. If an optional limit is provided, additionally restrict the number of hardware breakpoints for remote targets. Example: "hardwareBreakpoints": { "require": true, "limit": 5 }. /// [JsonProperty("hardwareBreakpoints", DefaultValueHandling = DefaultValueHandling.Ignore)] - public HardwareBreakpointInfo HardwareBreakpointInfo { get; set; } + public HardwareBreakpointInfo? HardwareBreakpointInfo { get; set; } /// /// Controls how breakpoints set externally (usually via raw GDB commands) are handled when hit. "throw" acts as if an exception was thrown by the application and "stop" only pauses the debug session. @@ -127,21 +126,23 @@ public abstract partial class BaseOptions /// Controls GDB's debuginfod behavior. /// [JsonProperty("debuginfod", DefaultValueHandling = DefaultValueHandling.Ignore)] - public DebuginfodSettings Debuginfod { get; set; } + public DebuginfodSettings? Debuginfod { get; set; } } internal class VisualizerFileConverter : JsonConverter { - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { - List visualizerFile = new List(); + List? visualizerFile = null; if (reader.TokenType == JsonToken.StartArray) { visualizerFile = serializer.Deserialize>(reader); } else if (reader.TokenType == JsonToken.String) { - visualizerFile.Add(reader.Value.ToString()); + object? value = reader.Value; + Debug.Assert(value is not null, "Should be impossible -- `TokenType == JsonToken.String` means `Value` cannot be null"); + visualizerFile = new List { value.ToString() }; } else { @@ -155,7 +156,7 @@ public override bool CanConvert(Type objectType) throw new NotImplementedException(); } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { throw new NotImplementedException(); } @@ -180,20 +181,20 @@ public AttachOptions() public AttachOptions( string program, int processId, - string type = null, - string targetArchitecture = null, - List visualizerFile = null, + string? type = null, + string? targetArchitecture = null, + List? visualizerFile = null, bool? showDisplayString = null, - string additionalSOLibSearchPath = null, - string MIMode = null, - string miDebuggerPath = null, - string miDebuggerArgs = null, - string miDebuggerServerAddress = null, + string? additionalSOLibSearchPath = null, + string? MIMode = null, + string? miDebuggerPath = null, + string? miDebuggerArgs = null, + string? miDebuggerServerAddress = null, bool? useExtendedRemote = null, - HardwareBreakpointInfo hardwareBreakpointInfo = null, - Dictionary sourceFileMap = null, - PipeTransport pipeTransport = null, - SymbolLoadInfo symbolLoadInfo = null) + HardwareBreakpointInfo? hardwareBreakpointInfo = null, + Dictionary? sourceFileMap = null, + PipeTransport? pipeTransport = null, + SymbolLoadInfo? symbolLoadInfo = null) { this.Program = program; this.Type = type; @@ -221,10 +222,10 @@ public partial class Environment #region Public Properties for Serialization [JsonProperty("name", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string Name { get; set; } + public string? Name { get; set; } [JsonProperty("value", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string Value { get; set; } + public string? Value { get; set; } #endregion @@ -234,7 +235,7 @@ public Environment() { } - public Environment(string name = null, string value = null) + public Environment(string? name = null, string? value = null) { this.Name = name; this.Value = value; @@ -259,7 +260,7 @@ public partial class SymbolLoadInfo /// Otherwise only load symbols for libs that match. /// [JsonProperty("exceptionList", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string ExceptionList { get; set; } + public string? ExceptionList { get; set; } #endregion @@ -269,7 +270,7 @@ public SymbolLoadInfo() { } - public SymbolLoadInfo(bool? loadAll = null, string exceptionList = null) + public SymbolLoadInfo(bool? loadAll = null, string? exceptionList = null) { this.LoadAll = loadAll; this.ExceptionList = exceptionList; @@ -344,19 +345,19 @@ public partial class LaunchOptions : BaseOptions /// Command line arguments passed to the program. /// [JsonProperty("args", DefaultValueHandling = DefaultValueHandling.Ignore)] - public List Args { get; private set; } + public List? Args { get; private set; } /// /// The working directory of the target /// [JsonProperty("cwd", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string Cwd { get; set; } + public string? Cwd { get; set; } /// /// If provided, this replaces the default commands used to launch a target with some other commands. For example, this can be "-target-attach" in order to attach to a target process. An empty command list replaces the launch commands with nothing, which can be useful if the debugger is being provided launch options as command line options. Example: "customLaunchSetupCommands": [ { "text": "target-run", "description": "run target", "ignoreFailures": false }]. /// [JsonProperty("customLaunchSetupCommands", DefaultValueHandling = DefaultValueHandling.Ignore)] - public List CustomLaunchSetupCommands { get; private set; } + public List? CustomLaunchSetupCommands { get; private set; } /// /// The command to execute after the debugger is fully setup in order to cause the target process to run. Allowed values are "exec-run", "exec-continue", "None". The default value is "exec-run". @@ -369,7 +370,7 @@ public partial class LaunchOptions : BaseOptions /// Environment variables to add to the environment for the program. Example: [ { "name": "squid", "value": "clam" } ]. /// [JsonProperty("environment", DefaultValueHandling = DefaultValueHandling.Ignore)] - public List Environment { get; private set; } + public List? Environment { get; private set; } /// /// Optional parameter. If true, the debugger should stop at the entrypoint of the target. If processId is passed, has no effect. @@ -381,19 +382,19 @@ public partial class LaunchOptions : BaseOptions /// Optional full path to debug server to launch. Defaults to null. /// [JsonProperty("debugServerPath", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string DebugServerPath { get; set; } + public string? DebugServerPath { get; set; } /// /// Optional debug server args. Defaults to null. /// [JsonProperty("debugServerArgs", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string DebugServerArgs { get; set; } + public string? DebugServerArgs { get; set; } /// /// Optional server-started pattern to look for in the debug server output. Defaults to null. /// [JsonProperty("serverStarted", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string ServerStarted { get; set; } + public string? ServerStarted { get; set; } /// /// Optional time, in milliseconds, for the debugger to wait for the debugServer to start up. Default is 10000. @@ -417,7 +418,7 @@ public partial class LaunchOptions : BaseOptions /// Optional full path to a core dump file for the specified program. Defaults to null. /// [JsonProperty("coreDumpPath", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string CoreDumpPath { get; set; } + public string? CoreDumpPath { get; set; } /// /// If true, a console is launched for the debuggee. If false, no console is launched. Note this option is ignored in some cases for technical reasons. @@ -453,35 +454,35 @@ public LaunchOptions() public LaunchOptions( string program, - List args = null, - string type = null, - string targetArchitecture = null, - string cwd = null, - List setupCommands = null, - List postRemoteConnectCommands = null, - List customLaunchSetupCommands = null, + List? args = null, + string? type = null, + string? targetArchitecture = null, + string? cwd = null, + List? setupCommands = null, + List? postRemoteConnectCommands = null, + List? customLaunchSetupCommands = null, LaunchCompleteCommand? launchCompleteCommand = null, - List visualizerFile = null, + List? visualizerFile = null, bool? showDisplayString = null, - List environment = null, - string additionalSOLibSearchPath = null, - string MIMode = null, - string miDebuggerPath = null, - string miDebuggerArgs = null, - string miDebuggerServerAddress = null, + List? environment = null, + string? additionalSOLibSearchPath = null, + string? MIMode = null, + string? miDebuggerPath = null, + string? miDebuggerArgs = null, + string? miDebuggerServerAddress = null, bool? useExtendedRemote = null, bool? stopAtEntry = null, - string debugServerPath = null, - string debugServerArgs = null, - string serverStarted = null, + string? debugServerPath = null, + string? debugServerArgs = null, + string? serverStarted = null, bool? filterStdout = null, bool? filterStderr = null, int? serverLaunchTimeout = null, - string coreDumpPath = null, + string? coreDumpPath = null, bool? externalConsole = null, - HardwareBreakpointInfo hardwareBreakpointInfo = null, - Dictionary sourceFileMap = null, - PipeTransport pipeTransport = null, + HardwareBreakpointInfo? hardwareBreakpointInfo = null, + Dictionary? sourceFileMap = null, + PipeTransport? pipeTransport = null, bool? stopAtConnect = null) { this.Program = program; @@ -525,11 +526,11 @@ public LaunchOptions( /// private class LaunchCompleteCommandConverter : JsonConverter { - public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer) { if (objectType == typeof(LaunchCompleteCommand?) && reader.TokenType == JsonToken.String) { - String value = reader.Value.ToString(); + string value = reader.Value!.ToString(); if (value.Equals("exec-continue", StringComparison.Ordinal)) { return MICore.LaunchCompleteCommand.ExecContinue; @@ -546,8 +547,7 @@ public override object ReadJson(JsonReader reader, Type objectType, object exist throw new InvalidLaunchOptionsException(String.Format(CultureInfo.CurrentCulture, MICoreResources.Error_InvalidLaunchCompleteCommandValue, reader.Value)); } - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Unexpected objectType '{0}' passed for launchCompleteCommand serialization.", objectType.ToString())); - return null; + throw new InvalidLaunchOptionsException(String.Format(CultureInfo.CurrentCulture, "Unexpected objectType '{0}' passed for launchCompleteCommand serialization.", objectType.ToString())); } public override bool CanConvert(Type objectType) @@ -555,7 +555,7 @@ public override bool CanConvert(Type objectType) return objectType == typeof(LaunchCompleteCommand?); } - public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer) { throw new NotImplementedException(); } @@ -571,19 +571,19 @@ public partial class PipeTransport : PipeTransportOptions /// When present, this tells the debugger override the PipeTransport's fields if the client's current platform is Windows and the field is defined in this configuration. /// [JsonProperty("windows", DefaultValueHandling = DefaultValueHandling.Ignore)] - public PipeTransportOptions Windows { get; private set; } + public PipeTransportOptions? Windows { get; private set; } /// /// When present, this tells the debugger override the PipeTransport's fields if the client's current platform is OSX and the field is defined in this configuration. /// [JsonProperty("osx", DefaultValueHandling = DefaultValueHandling.Ignore)] - public PipeTransportOptions OSX { get; private set; } + public PipeTransportOptions? OSX { get; private set; } /// /// When present, this tells the debugger override the PipeTransport's fields if the client's current platform is Linux and the field is defined in this configuration. /// [JsonProperty("linux", DefaultValueHandling = DefaultValueHandling.Ignore)] - public PipeTransportOptions Linux { get; private set; } + public PipeTransportOptions? Linux { get; private set; } #endregion @@ -594,7 +594,7 @@ public PipeTransport() } - public PipeTransport(PipeTransportOptions windows = null, PipeTransportOptions osx = null, PipeTransportOptions linux = null) + public PipeTransport(PipeTransportOptions? windows = null, PipeTransportOptions? osx = null, PipeTransportOptions? linux = null) { this.Windows = windows; this.OSX = osx; @@ -613,37 +613,37 @@ public partial class PipeTransportOptions /// The fully qualified path to the working directory for the pipe program. /// [JsonProperty("pipeCwd", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string PipeCwd { get; set; } + public string? PipeCwd { get; set; } /// /// The fully qualified pipe command to execute. /// [JsonProperty("pipeProgram", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string PipeProgram { get; set; } + public string? PipeProgram { get; set; } /// /// Command line arguments passed to the pipe program to configure the connection. /// [JsonProperty("pipeArgs", DefaultValueHandling = DefaultValueHandling.Ignore)] - public List PipeArgs { get; private set; } + public List? PipeArgs { get; private set; } /// /// Command line arguments passed to the pipe program to execute a remote command. /// [JsonProperty("pipeCmd", DefaultValueHandling = DefaultValueHandling.Ignore)] - public List PipeCmd { get; private set; } + public List? PipeCmd { get; private set; } /// /// The full path to the debugger on the target machine, for example /usr/bin/gdb. /// [JsonProperty("debuggerPath", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string DebuggerPath { get; set; } + public string? DebuggerPath { get; set; } /// /// Environment variables passed to the pipe program. /// [JsonProperty("pipeEnv", DefaultValueHandling = DefaultValueHandling.Ignore)] - public Dictionary PipeEnv { get; private set; } + public Dictionary? PipeEnv { get; private set; } /// /// Should arguments that contain characters that need to be quoted (example: spaces) be quoted? Defaults to 'true'. If set to false, the debugger command will no longer be automatically quoted. @@ -658,10 +658,10 @@ public partial class PipeTransportOptions public PipeTransportOptions() { this.PipeArgs = new List(); - this.PipeEnv = new Dictionary(); + this.PipeEnv = new Dictionary(); } - public PipeTransportOptions(string pipeCwd = null, string pipeProgram = null, List pipeArgs = null, string debuggerPath = null, Dictionary pipeEnv = null, bool? quoteArgs = null) + public PipeTransportOptions(string? pipeCwd = null, string? pipeProgram = null, List? pipeArgs = null, string? debuggerPath = null, Dictionary? pipeEnv = null, bool? quoteArgs = null) { this.PipeCwd = pipeCwd; this.PipeProgram = pipeProgram; @@ -682,13 +682,13 @@ public partial class SetupCommand /// The debugger command to execute. /// [JsonProperty("text", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string Text { get; set; } + public string? Text { get; set; } /// /// Optional description for the command. /// [JsonProperty("description", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string Description { get; set; } + public string? Description { get; set; } /// /// If true, failures from the command should be ignored. Default value is false. @@ -704,7 +704,7 @@ public SetupCommand() { } - public SetupCommand(string text = null, string description = null, bool? ignoreFailures = null) + public SetupCommand(string? text = null, string? description = null, bool? ignoreFailures = null) { this.Text = text; this.Description = description; @@ -722,7 +722,7 @@ public partial class SourceFileMapOptions /// The editor's path. /// [JsonProperty("editorPath", DefaultValueHandling = DefaultValueHandling.Ignore)] - public string EditorPath { get; set; } + public string? EditorPath { get; set; } /// /// Use this source mapping for breakpoint binding? Default is true. @@ -738,7 +738,7 @@ public SourceFileMapOptions() { } - public SourceFileMapOptions(string editorPath = null, bool? useForBreakpoints = null) + public SourceFileMapOptions(string? editorPath = null, bool? useForBreakpoints = null) { this.EditorPath = editorPath; this.UseForBreakpoints = useForBreakpoints; @@ -751,16 +751,16 @@ public static class LaunchOptionHelpers { public static BaseOptions GetLaunchOrAttachOptions(JObject parsedJObject) { - BaseOptions baseOptions = null; - string requestType = parsedJObject["request"]?.Value(); - if (String.IsNullOrWhiteSpace(requestType)) + BaseOptions? baseOptions = null; + string? requestType = parsedJObject["request"]?.Value(); + if (IsNullOrWhiteSpace(requestType)) { // If request isn't specified, see if we can determine what it is - if (!String.IsNullOrWhiteSpace(parsedJObject["processId"]?.Value())) + if (!IsNullOrWhiteSpace(parsedJObject["processId"]?.Value())) { requestType = "attach"; } - else if (!String.IsNullOrWhiteSpace(parsedJObject["program"]?.Value())) + else if (!IsNullOrWhiteSpace(parsedJObject["program"]?.Value())) { requestType = "launch"; } @@ -784,6 +784,11 @@ public static BaseOptions GetLaunchOrAttachOptions(JObject parsedJObject) throw new InvalidLaunchOptionsException(String.Format(CultureInfo.CurrentCulture, MICoreResources.Error_BadRequiredAttribute, "request")); } + if (baseOptions is null) + { + throw new InvalidLaunchOptionsException(String.Format(CultureInfo.CurrentCulture, MICoreResources.Error_BadRequiredAttribute, "request")); + } + return baseOptions; } } diff --git a/src/MICore/LaunchCommand.cs b/src/MICore/LaunchCommand.cs index 64941087a..af2db9189 100644 --- a/src/MICore/LaunchCommand.cs +++ b/src/MICore/LaunchCommand.cs @@ -21,11 +21,11 @@ public class LaunchCommand public readonly string Description; public readonly bool IgnoreFailures; public readonly bool IsMICommand; - public /*OPTIONAL*/ Action FailureHandler { get; private set; } - public /*OPTIONAL*/ Func SuccessHandler { get; private set; } - public /*Optional*/ Func SuccessResultsHandler { get; private set; } + public Action? FailureHandler { get; private set; } + public Func? SuccessHandler { get; private set; } + public Func? SuccessResultsHandler { get; private set; } - public LaunchCommand(string commandText, string description = null, bool ignoreFailures = false, Action failureHandler = null, Func successHandler = null, Func successResultsHandler = null) + public LaunchCommand(string commandText, string? description = null, bool ignoreFailures = false, Action? failureHandler = null, Func? successHandler = null, Func? successResultsHandler = null) { if (commandText == null) throw new ArgumentNullException(nameof(commandText)); @@ -34,9 +34,7 @@ public LaunchCommand(string commandText, string description = null, bool ignoreF throw new ArgumentOutOfRangeException(nameof(commandText)); this.IsMICommand = commandText[0] == '-'; this.CommandText = commandText; - this.Description = description; - if (string.IsNullOrWhiteSpace(description)) - this.Description = this.CommandText; + this.Description = IsNullOrWhiteSpace(description) ? this.CommandText : description; this.IgnoreFailures = ignoreFailures; this.FailureHandler = failureHandler; @@ -46,7 +44,10 @@ public LaunchCommand(string commandText, string description = null, bool ignoreF public static ReadOnlyCollection CreateCollection(List source) { - IList commands = source?.Select(x => new LaunchCommand(x.Text, x.Description, x.IgnoreFailures.GetValueOrDefault(false))).ToList(); + IList? commands = source + ?.Where(x => !string.IsNullOrWhiteSpace(x.Text)) + ?.Select(x => new LaunchCommand(x.Text!, x.Description, x.IgnoreFailures.GetValueOrDefault(false))) + ?.ToList(); if(commands == null) { commands = new List(0); @@ -57,7 +58,7 @@ public static ReadOnlyCollection CreateCollection(List CreateCollection(Xml.LaunchOptions.Command[] source) { - LaunchCommand[] commandArray = source?.Select(x => new LaunchCommand(x.Value, x.Description, x.IgnoreFailures)).ToArray(); + LaunchCommand[]? commandArray = source?.Select(x => new LaunchCommand(x.Value, x.Description, x.IgnoreFailures)).ToArray(); if (commandArray == null) { commandArray = new LaunchCommand[0]; diff --git a/src/MICore/LaunchOptions.cs b/src/MICore/LaunchOptions.cs index 2a8594fe7..5f39eca51 100644 --- a/src/MICore/LaunchOptions.cs +++ b/src/MICore/LaunchOptions.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -21,6 +20,7 @@ using Newtonsoft.Json.Linq; using System.Text; using MICore.Json.LaunchOptions; +using System.Diagnostics.CodeAnalysis; namespace MICore { @@ -74,18 +74,18 @@ public sealed class PipeLaunchOptions : LaunchOptions /// Command to be invoked on the pipe program /// Current working directory of pipe program. If empty directory of the pipePath is set as the cwd. /// Environment variables set before invoking the pipe program - public PipeLaunchOptions(string pipePath, string pipeArguments, string pipeCommandArguments, string pipeCwd, MICore.Xml.LaunchOptions.EnvironmentEntry[] pipeEnvironment) + public PipeLaunchOptions(string pipePath, string? pipeArguments, string? pipeCommandArguments, string? pipeCwd, MICore.Xml.LaunchOptions.EnvironmentEntry[]? pipeEnvironment) : this(pipePath, pipeArguments, pipeCommandArguments, pipeCwd, (pipeEnvironment != null) ? pipeEnvironment.Select(e => new EnvironmentEntry(e)).ToArray() : new EnvironmentEntry[] { }) { } - public PipeLaunchOptions(string pipePath, string pipeArguments, string pipeCommandArguments, string pipeCwd, IList pipeEnvironment) + public PipeLaunchOptions(string pipePath, string? pipeArguments, string? pipeCommandArguments, string? pipeCwd, IList? pipeEnvironment) { - if (string.IsNullOrEmpty(pipePath)) + if (IsNullOrEmpty(pipePath)) throw new ArgumentNullException(nameof(pipePath)); this.PipePath = pipePath; this.PipeArguments = pipeArguments; - this.PipeCommandArguments = pipeCommandArguments; + this.PipeCommandArguments = pipeCommandArguments ?? string.Empty; this.PipeCwd = pipeCwd; this.PipeEnvironment = new ReadOnlyCollection(pipeEnvironment ?? new List(0)); @@ -94,25 +94,29 @@ public PipeLaunchOptions(string pipePath, string pipeArguments, string pipeComma private static string gdbPathDefault = @"/usr/bin/gdb"; static internal PipeLaunchOptions CreateFromJson(JObject parsedOptions) { - Debug.Assert(parsedOptions["pipeTransport"] != null && parsedOptions["pipeTransport"].HasValues, "PipeTransport should exist and have values."); + JToken? pipeTransportToken = parsedOptions["pipeTransport"]; + if (pipeTransportToken is null || !pipeTransportToken.HasValues) + { + throw new InvalidLaunchOptionsException(MICoreResources.Error_UnknownLaunchOptions); + } - Json.LaunchOptions.PipeTransport pipeTransport = parsedOptions["pipeTransport"].ToObject(); + Json.LaunchOptions.PipeTransport pipeTransport = pipeTransportToken.ToObject() ?? throw new InvalidLaunchOptionsException(MICoreResources.Error_UnknownLaunchOptions); // PipeProgram must be specified - if (String.IsNullOrWhiteSpace(pipeTransport.PipeProgram)) + string? pipeProgram = pipeTransport.PipeProgram; + if (IsNullOrWhiteSpace(pipeProgram)) { throw new InvalidLaunchOptionsException(String.Format(CultureInfo.CurrentCulture, MICoreResources.Error_EmptyPipePath)); } - string pipeCwd = pipeTransport.PipeCwd; - string pipeProgram = pipeTransport.PipeProgram; - List pipeArgs = pipeTransport.PipeArgs; - List pipeCmd = pipeTransport.PipeCmd; - string debuggerPath = pipeTransport.DebuggerPath; + string? pipeCwd = pipeTransport.PipeCwd; + List? pipeArgs = pipeTransport.PipeArgs; + List? pipeCmd = pipeTransport.PipeCmd; + string? debuggerPath = pipeTransport.DebuggerPath; bool quoteArgs = pipeTransport.QuoteArgs.GetValueOrDefault(true); - Dictionary pipeEnv = pipeTransport.PipeEnv; + Dictionary? pipeEnv = pipeTransport.PipeEnv; - Json.LaunchOptions.PipeTransportOptions platformSpecificTransportOptions = null; + Json.LaunchOptions.PipeTransportOptions? platformSpecificTransportOptions = null; if (PlatformUtilities.IsOSX() && pipeTransport.OSX != null) { platformSpecificTransportOptions = pipeTransport.OSX; @@ -161,10 +165,10 @@ static internal PipeLaunchOptions CreateFromJson(JObject parsedOptions) return pipeOptions; } - private static string EnsurePipeArguments(List pipeArgs, string debuggerPath, string debuggerPathDefault, bool quoteArgs) + private static string EnsurePipeArguments(List? pipeArgs, string? debuggerPath, string debuggerPathDefault, bool quoteArgs) { // Debugger path. Assume /usr/bin/gdb unless specified - string dbgPath = String.IsNullOrWhiteSpace(debuggerPath) ? debuggerPathDefault : debuggerPath; + string dbgPath = IsNullOrWhiteSpace(debuggerPath) ? debuggerPathDefault : debuggerPath; // debugger command: /usr/bin/gdb --interpreter=mi string dbgCmdArguments = String.Format(CultureInfo.InvariantCulture, "{0} {1}", dbgPath, "--interpreter=mi"); @@ -193,11 +197,11 @@ internal static string ReplaceDebuggerCommandToken(string cmdArgs, string comman } } - private static IList GetEnvironmentEntries(IDictionary env) + private static IList GetEnvironmentEntries(IDictionary? env) { List entries = new List(); - if (env != null && env.Keys.Any()) + if (env is not null) { foreach (var key in env.Keys) { @@ -225,7 +229,7 @@ static internal PipeLaunchOptions CreateFromXml(Xml.LaunchOptions.PipeLaunchOpti /// [Optional] Arguments to pass to the pipe executable. /// /// - public string PipeArguments { get; private set; } + public string? PipeArguments { get; private set; } /// /// [Optional] Arguments to pass to the PipePath program that include a format specifier ('{0}') for a custom command. @@ -235,7 +239,7 @@ static internal PipeLaunchOptions CreateFromXml(Xml.LaunchOptions.PipeLaunchOpti /// /// [Optional] Current working directory when the pipe program is invoked. /// - public string PipeCwd { get; private set; } + public string? PipeCwd { get; private set; } /// /// [Optional] Enviroment variables for the pipe program. @@ -247,7 +251,7 @@ public sealed class TcpLaunchOptions : LaunchOptions { public TcpLaunchOptions(string hostname, int port, bool secure) { - if (string.IsNullOrEmpty(hostname)) + if (IsNullOrEmpty(hostname)) { throw new ArgumentException(null, nameof(hostname)); } @@ -282,9 +286,9 @@ static internal TcpLaunchOptions CreateFromXml(Xml.LaunchOptions.TcpLaunchOption /// X509Chain object for the chain of certificate authorities associated with the remote certificate. /// One or more errors associated with the remote certificate. /// true if the specified certificate is accepted - public delegate bool MIServerCertificateValidationCallback(object sender, object/*X509Certificate*/ certificate, object/*X509Chain*/ chain, SslPolicyErrors sslPolicyErrors); + public delegate bool MIServerCertificateValidationCallback(object sender, object/*X509Certificate*/? certificate, object/*X509Chain*/? chain, SslPolicyErrors sslPolicyErrors); - public MIServerCertificateValidationCallback ServerCertificateValidationCallback { get; set; } + public MIServerCertificateValidationCallback? ServerCertificateValidationCallback { get; set; } } public sealed class EnvironmentEntry @@ -297,11 +301,11 @@ public EnvironmentEntry(Xml.LaunchOptions.EnvironmentEntry xmlEntry) public EnvironmentEntry(Json.LaunchOptions.Environment jsonEntry) { - this.Name = jsonEntry.Name; + this.Name = LaunchOptions.RequireAttribute(jsonEntry.Name, "environment.name"); this.Value = jsonEntry.Value; } - public EnvironmentEntry(string name, string value) + public EnvironmentEntry(string name, string? value) { this.Name = name; this.Value = value; @@ -313,9 +317,9 @@ public EnvironmentEntry(string name, string value) public string Name { get; private set; } /// - /// [Required] Value of the environment variable + /// Value of the environment variable, or null to delete it /// - public string Value { get; private set; } + public string? Value { get; private set; } } public sealed class SourceMapEntry @@ -331,7 +335,7 @@ public SourceMapEntry(Xml.LaunchOptions.SourceMapEntry xmlEntry) this.UseForBreakpoints = xmlEntry.UseForBreakpoints; } - private string _editorPath; + private string _editorPath = string.Empty; public string EditorPath { get @@ -340,7 +344,7 @@ public string EditorPath } set { - if (string.IsNullOrEmpty(value)) + if (IsNullOrEmpty(value)) { throw new ArgumentNullException("EditorPath"); } @@ -349,7 +353,7 @@ public string EditorPath } - private string _compileTimePath; + private string _compileTimePath = string.Empty; public string CompileTimePath { get @@ -363,9 +367,9 @@ public string CompileTimePath } public bool UseForBreakpoints { get; set; } - public static ReadOnlyCollection CreateCollection(Xml.LaunchOptions.SourceMapEntry[] source) + public static ReadOnlyCollection CreateCollection(Xml.LaunchOptions.SourceMapEntry[]? source) { - SourceMapEntry[] pathArray = source?.Select(x => new SourceMapEntry(x)).ToArray(); + SourceMapEntry[]? pathArray = source?.Select(x => new SourceMapEntry(x)).ToArray(); if (pathArray == null) { @@ -375,14 +379,19 @@ public static ReadOnlyCollection CreateCollection(Xml.LaunchOpti return new ReadOnlyCollection(pathArray); } - public static ReadOnlyCollection CreateCollection(Dictionary source) + public static ReadOnlyCollection CreateCollection(Dictionary? source) { - var sourceMaps = new List(source.Keys.Count); + int count = source?.Keys.Count ?? 0; + if (count == 0) + { + return new ReadOnlyCollection(Array.Empty()); + } - foreach (var item in source) + var sourceMaps = new List(); + foreach (var item in source!) { string compileTimePath = item.Key; - string editorPath = null; + string? editorPath = null; bool useForBreakpoints = true; if (item.Value is string value) @@ -394,7 +403,7 @@ public static ReadOnlyCollection CreateCollection(Dictionary(); + jObject.ToObject() ?? throw new InvalidLaunchOptionsException(String.Format(CultureInfo.CurrentCulture, MICoreResources.Error_SourceFileMapFormat, compileTimePath)); editorPath = sourceMapItem.EditorPath; useForBreakpoints = sourceMapItem.UseForBreakpoints.GetValueOrDefault(true); @@ -409,7 +418,7 @@ public static ReadOnlyCollection CreateCollection(Dictionary /// [Required] Arguments for the MI Debugger. /// - public string MIDebuggerArgs { get; private set; } + public string? MIDebuggerArgs { get; private set; } /// /// [Optional] Server address that MI Debugger server is listening to /// - public string MIDebuggerServerAddress { get; private set; } + public string? MIDebuggerServerAddress { get; private set; } /// /// [Optional] If true, use gdb extended-remote mode to connect to gdbserver. @@ -701,17 +714,17 @@ private static string EnsureDebuggerPath(string miDebuggerPath, string debuggerB /// /// [Optional] MI Debugger Server exe, if non-null then the MIEngine will start the debug server before starting the debugger /// - public string DebugServer { get; private set; } + public string? DebugServer { get; private set; } /// /// [Optional] Args for MI Debugger Server exe /// - public string DebugServerArgs { get; private set; } + public string? DebugServerArgs { get; private set; } /// /// [Optional] Server started pattern (in Regex format) /// - public string ServerStarted { get; private set; } + public string? ServerStarted { get; private set; } /// /// [Optional] Log strings written to stderr and examine for server started pattern @@ -781,15 +794,15 @@ public sealed class UnixShellPortLaunchOptions : LaunchOptions public string StartRemoteDebuggerCommand { get; private set; } public Microsoft.VisualStudio.Debugger.Interop.UnixPortSupplier.IDebugUnixShellPort UnixPort { get; private set; } - public UnixShellPortLaunchOptions(string startRemoteDebuggerCommand, + public UnixShellPortLaunchOptions(string? startRemoteDebuggerCommand, Microsoft.VisualStudio.Debugger.Interop.UnixPortSupplier.IDebugUnixShellPort unixPort, MIMode miMode, - BaseLaunchOptions baseLaunchOptions) + BaseLaunchOptions? baseLaunchOptions) { this.UnixPort = unixPort; this.DebuggerMIMode = miMode; - if (string.IsNullOrEmpty(startRemoteDebuggerCommand)) + if (IsNullOrEmpty(startRemoteDebuggerCommand)) { switch (miMode) { @@ -814,7 +827,7 @@ public UnixShellPortLaunchOptions(string startRemoteDebuggerCommand, } string prefix = GetDebuginfodEnvironmentPrefix(); - if (!string.IsNullOrEmpty(prefix)) + if (!IsNullOrEmpty(prefix)) { this.StartRemoteDebuggerCommand = prefix + this.StartRemoteDebuggerCommand; } @@ -827,14 +840,14 @@ public UnixShellPortLaunchOptions(string startRemoteDebuggerCommand, public abstract class LaunchOptions { private const string XmlNamespace = "http://schemas.microsoft.com/vstudio/MDDDebuggerOptions/2014"; - private static Lazy s_serializationAssembly = new Lazy(LoadSerializationAssembly, LazyThreadSafetyMode.ExecutionAndPublication); + private static Lazy s_serializationAssembly = new Lazy(LoadSerializationAssembly, LazyThreadSafetyMode.ExecutionAndPublication); private bool _initializationComplete; private MIMode _miMode; /// /// [Optional] Launcher used to start the application on the device /// - public IPlatformAppLauncher DeviceAppLauncher { get; private set; } + public IPlatformAppLauncher? DeviceAppLauncher { get; private set; } public MIMode DebuggerMIMode { @@ -848,11 +861,11 @@ public MIMode DebuggerMIMode public bool NoDebug { get; private set; } = false; - private Xml.LaunchOptions.BaseLaunchOptions _baseOptions; + private Xml.LaunchOptions.BaseLaunchOptions? _baseOptions; /// /// Hold on to options in serializable form to support child process debugging /// - public Xml.LaunchOptions.BaseLaunchOptions BaseOptions + public Xml.LaunchOptions.BaseLaunchOptions? BaseOptions { get { return _baseOptions; } protected set @@ -865,18 +878,18 @@ protected set } } - private string _exePath; + private string? _exePath; /// - /// [Required] Path to the executable file. This could be a path on the remote machine (for Pipe transport) + /// Path to the executable file. This could be a path on the remote machine (for Pipe transport) /// or the local machine (Local transport). /// - public virtual string ExePath + public virtual string? ExePath { get { return _exePath; } set { - if (string.IsNullOrWhiteSpace(value)) + if (IsNullOrWhiteSpace(value)) throw new ArgumentOutOfRangeException("ExePath"); VerifyCanModifyProperty(nameof(ExePath)); @@ -884,13 +897,13 @@ public virtual string ExePath } } - private string _exeArguments; + private string? _exeArguments; /// - /// [Optional] Additional arguments to specify when launching the process + /// Additional arguments to specify when launching the process /// public string ExeArguments { - get { return _exeArguments; } + get => _exeArguments ?? string.Empty; set { VerifyCanModifyProperty(nameof(ExeArguments)); @@ -913,11 +926,11 @@ protected set } } - private string _coreDumpPath; + private string? _coreDumpPath; /// /// [Optional] Path to a core dump file for the specified executable. /// - public string CoreDumpPath + public string? CoreDumpPath { get { @@ -931,16 +944,18 @@ protected set _coreDumpPath = value; } } + + [MemberNotNullWhen(true, nameof(CoreDumpPath))] public bool IsCoreDump { - get { return !String.IsNullOrEmpty(this.CoreDumpPath); } + get { return !IsNullOrEmpty(this.CoreDumpPath); } } - private string _workingDirectory; + private string? _workingDirectory; /// /// [Optional] Working directory to use for the MI Debugger when launching the process /// - public string WorkingDirectory + public string? WorkingDirectory { get { return _workingDirectory; } set @@ -950,11 +965,11 @@ public string WorkingDirectory } } - private string _absolutePrefixSoLibSearchPath; + private string? _absolutePrefixSoLibSearchPath; /// /// [Optional] Absolute prefix for directories to search for shared library symbols /// - public string AbsolutePrefixSOLibSearchPath + public string? AbsolutePrefixSOLibSearchPath { get { return _absolutePrefixSoLibSearchPath; } set @@ -964,11 +979,11 @@ public string AbsolutePrefixSOLibSearchPath } } - private string _additionalSOLibSearchPath; + private string? _additionalSOLibSearchPath; /// /// [Optional] Additional directories to search for shared library symbols /// - public string AdditionalSOLibSearchPath + public string? AdditionalSOLibSearchPath { get { return _additionalSOLibSearchPath; } set @@ -1071,14 +1086,18 @@ public bool UseUnixSymbolPaths } } - private ReadOnlyCollection _setupCommands; + private ReadOnlyCollection? _setupCommands; /// /// [Required] Additional commands used to setup debugging. May be an empty collection /// public ReadOnlyCollection SetupCommands { - get { return _setupCommands; } + get + { + _setupCommands ??= new ReadOnlyCollection(Array.Empty()); + return _setupCommands; + } set { if (value == null) @@ -1089,14 +1108,18 @@ public ReadOnlyCollection SetupCommands } } - private ReadOnlyCollection _postRemoteConnectCommands; + private ReadOnlyCollection? _postRemoteConnectCommands; /// /// [Required] Additional commands used to setup debugging once the remote connection has been made. May be an empty collection /// public ReadOnlyCollection PostRemoteConnectCommands { - get { return _postRemoteConnectCommands; } + get + { + _postRemoteConnectCommands ??= new ReadOnlyCollection(Array.Empty()); + return _postRemoteConnectCommands; + } set { if (value == null) @@ -1108,14 +1131,14 @@ public ReadOnlyCollection PostRemoteConnectCommands } - private ReadOnlyCollection _customLaunchSetupCommands; + private ReadOnlyCollection? _customLaunchSetupCommands; /// /// [Optional] If provided, this replaces the default commands used to launch a target with some other commands. For example, /// this can be '-target-attach' in order to attach to a target process.An empty command list replaces the launch commands with nothing, /// which can be useful if the debugger is being provided launch options as command line options. /// - public ReadOnlyCollection CustomLaunchSetupCommands + public ReadOnlyCollection? CustomLaunchSetupCommands { get { return _customLaunchSetupCommands; } set @@ -1149,9 +1172,9 @@ protected set } } - private ReadOnlyCollection _sourceMap; + private ReadOnlyCollection? _sourceMap; - public ReadOnlyCollection SourceMap + public ReadOnlyCollection? SourceMap { get { return _sourceMap; } set @@ -1335,48 +1358,55 @@ public string GetOptionsString() } } - public static LaunchOptions GetInstance(HostConfigurationStore configStore, string exePath, string args, string dir, string options, bool noDebug, IDeviceAppLauncherEventCallback eventCallback, TargetEngine targetEngine, Logger logger) + public static LaunchOptions GetInstance(HostConfigurationStore? configStore, string exePath, string? args, string? dir, string? options, bool noDebug, IDeviceAppLauncherEventCallback? eventCallback, TargetEngine targetEngine, Logger? logger) { - if (string.IsNullOrWhiteSpace(exePath)) + if (IsNullOrWhiteSpace(exePath)) throw new ArgumentNullException(nameof(exePath)); options = options?.Trim(); - if (string.IsNullOrEmpty(options)) + if (IsNullOrEmpty(options)) throw new InvalidLaunchOptionsException(MICoreResources.Error_StringIsNullOrEmpty); logger?.WriteTextBlock(LogLevel.Verbose, "LaunchOptions", options); - LaunchOptions launchOptions = null; + LaunchOptions? launchOptions = null; Guid clsidLauncher = Guid.Empty; - object launcher = null; - object launcherXmlOptions = null; + object? launcher = null; + object? launcherXmlOptions = null; if (options[0] == '{') { try { - JObject parsedOptions = JsonConvert.DeserializeObject(options, new JsonSerializerSettings { DateParseHandling = DateParseHandling.None }); + JObject? parsedOptions = JsonConvert.DeserializeObject(options, new JsonSerializerSettings { DateParseHandling = DateParseHandling.None }); if (parsedOptions is null) { throw new InvalidLaunchOptionsException(MICoreResources.Error_UnknownLaunchOptions); } // if the customLauncher element is present then try using the custom launcher implementation from the config store - if (parsedOptions["customLauncher"] != null && !string.IsNullOrWhiteSpace(parsedOptions["customLauncher"].Value())) + JToken? customLauncherToken = parsedOptions["customLauncher"]; + if (customLauncherToken is not null && customLauncherToken.Value() is string customLauncherName && !IsNullOrWhiteSpace(customLauncherName)) { - string customLauncherName = parsedOptions["customLauncher"].Value(); - var jsonLauncher = configStore?.GetCustomLauncher(customLauncherName); + if (configStore is null || eventCallback is null) + { + throw new InvalidLaunchOptionsException(MICoreResources.Error_UnknownLaunchOptions); + } + + var jsonLauncher = configStore.GetCustomLauncher(customLauncherName); if (jsonLauncher == null) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_UnknownCustomLauncher, customLauncherName)); } - if (jsonLauncher as IPlatformAppLauncher == null) + + if (jsonLauncher is not IPlatformAppLauncher platformAppLauncher) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_LauncherNotFound, customLauncherName)); } - launchOptions = ExecuteLauncher(configStore, (IPlatformAppLauncher)jsonLauncher, exePath, args, dir, parsedOptions, eventCallback, targetEngine, logger); + + launchOptions = ExecuteLauncher(configStore, platformAppLauncher, exePath, args, dir, parsedOptions, eventCallback, targetEngine, logger); } - else if (parsedOptions["pipeTransport"] != null && parsedOptions["pipeTransport"].HasValues) + else if (parsedOptions["pipeTransport"] is JToken pipeTransportToken && pipeTransportToken.HasValues) { launchOptions = PipeLaunchOptions.CreateFromJson(parsedOptions); } @@ -1487,23 +1517,38 @@ public static LaunchOptions GetInstance(HostConfigurationStore configStore, stri if (clsidLauncher != Guid.Empty) { + if (launcherXmlOptions is null || configStore is null || eventCallback is null) + { + throw new InvalidLaunchOptionsException(MICoreResources.Error_UnknownLaunchOptions); + } + launchOptions = ExecuteLauncher(configStore, clsidLauncher, exePath, args, dir, launcherXmlOptions, eventCallback, targetEngine, logger); } else if (launcher != null) { + if (launcherXmlOptions is null || configStore is null || eventCallback is null) + { + throw new InvalidLaunchOptionsException(MICoreResources.Error_UnknownLaunchOptions); + } + launchOptions = ExecuteLauncher(configStore, (IPlatformAppLauncher)launcher, exePath, args, dir, launcherXmlOptions, eventCallback, targetEngine, logger); } + if (launchOptions is null) + { + throw new InvalidLaunchOptionsException(MICoreResources.Error_UnknownLaunchOptions); + } + if (targetEngine == TargetEngine.Native) { if (launchOptions.ExePath == null) launchOptions.ExePath = exePath; } - if (string.IsNullOrEmpty(launchOptions.ExeArguments)) - launchOptions.ExeArguments = args; + if (IsNullOrEmpty(launchOptions.ExeArguments)) + launchOptions.ExeArguments = args ?? string.Empty; - if (string.IsNullOrEmpty(launchOptions.WorkingDirectory)) + if (IsNullOrEmpty(launchOptions.WorkingDirectory)) launchOptions.WorkingDirectory = dir; launchOptions.NoDebug = noDebug; @@ -1524,22 +1569,22 @@ public static LaunchOptions CreateForAttachRequest(Microsoft.VisualStudio.Debugg Logger logger) { var suppOptions = GetOptionsFromFile(logger); - string connection; + string? connection; ((IDebugPort2)unixPort).GetPortName(out connection); - AttachOptionsForConnection attachOptions = null; + AttachOptionsForConnection? attachOptions = null; if (suppOptions != null && suppOptions.AttachOptions != null) { - attachOptions = suppOptions.AttachOptions.FirstOrDefault((o) => o.ConnectionName == connection || o.ConnectionName == "*" || string.IsNullOrWhiteSpace(o.ConnectionName)); + attachOptions = suppOptions.AttachOptions.FirstOrDefault((o) => o.ConnectionName == connection || o.ConnectionName == "*" || IsNullOrWhiteSpace(o.ConnectionName)); } - bool isServerMode = attachOptions?.ServerOptions != null; LaunchOptions options; - if (isServerMode && unixPort is Microsoft.VisualStudio.Debugger.Interop.UnixPortSupplier.IDebugGdbServerAttach) + ServerOptions? serverOptions = attachOptions?.ServerOptions; + if (serverOptions is not null && unixPort is Microsoft.VisualStudio.Debugger.Interop.UnixPortSupplier.IDebugGdbServerAttach gdbServerAttachPort) { - string addr = ((Microsoft.VisualStudio.Debugger.Interop.UnixPortSupplier.IDebugGdbServerAttach)unixPort).GdbServerAttachProcess(processId, attachOptions.ServerOptions.PreAttachCommand); - options = new LocalLaunchOptions(attachOptions.ServerOptions.MIDebuggerPath, addr, attachOptions.ServerOptions.MIDebuggerArgs); + string addr = gdbServerAttachPort.GdbServerAttachProcess(processId, serverOptions.PreAttachCommand); + options = new LocalLaunchOptions(serverOptions.MIDebuggerPath, addr, serverOptions.MIDebuggerArgs); options._miMode = miMode; - options.ExePath = attachOptions.ServerOptions.ExePath; + options.ExePath = serverOptions.ExePath; } else { @@ -1561,10 +1606,10 @@ public static LaunchOptions CreateForAttachRequest(Microsoft.VisualStudio.Debugg return options; } - internal static SupplementalLaunchOptions GetOptionsFromFile(Logger logger) + internal static SupplementalLaunchOptions? GetOptionsFromFile(Logger? logger) { // load supplemental options from the solution root - string slnRoot = null; + string? slnRoot = null; // During glass testing, the Shell assembly is not available try @@ -1574,18 +1619,18 @@ internal static SupplementalLaunchOptions GetOptionsFromFile(Logger logger) catch (FileNotFoundException) { } - if (!string.IsNullOrEmpty(slnRoot)) + if (!IsNullOrEmpty(slnRoot)) { string optFile = Path.Combine(slnRoot, "Microsoft.MIEngine.Options.xml"); if (File.Exists(optFile)) { - string suppOptions = null; + string? suppOptions = null; using (var reader = File.OpenText(optFile)) { suppOptions = reader.ReadToEnd(); } - if (!string.IsNullOrEmpty(suppOptions)) + if (!IsNullOrEmpty(suppOptions)) { try { @@ -1604,7 +1649,7 @@ internal static SupplementalLaunchOptions GetOptionsFromFile(Logger logger) return null; } - internal void LoadSupplementalOptions(Logger logger) + internal void LoadSupplementalOptions(Logger? logger) { if (SourceMap == null) { @@ -1615,7 +1660,7 @@ internal void LoadSupplementalOptions(Logger logger) Merge(options); } - void MergeMap(Xml.LaunchOptions.SourceMapEntry[] inMap) + void MergeMap(Xml.LaunchOptions.SourceMapEntry[]? inMap) { // merge the source mapping lists List map = new List(); @@ -1654,9 +1699,9 @@ private void Merge(AttachOptionsForConnection suppOptions) PostRemoteConnectCommands = new ReadOnlyCollection(postRemoteConnectCmds); MergeMap(suppOptions.SourceMap); - if (!string.IsNullOrWhiteSpace(suppOptions.AdditionalSOLibSearchPath)) + if (!IsNullOrWhiteSpace(suppOptions.AdditionalSOLibSearchPath)) { - if (string.IsNullOrWhiteSpace(AdditionalSOLibSearchPath)) + if (IsNullOrWhiteSpace(AdditionalSOLibSearchPath)) { AdditionalSOLibSearchPath = suppOptions.AdditionalSOLibSearchPath; } @@ -1665,7 +1710,7 @@ private void Merge(AttachOptionsForConnection suppOptions) AdditionalSOLibSearchPath += ';' + suppOptions.AdditionalSOLibSearchPath; } } - if (string.IsNullOrWhiteSpace(WorkingDirectory)) + if (IsNullOrWhiteSpace(WorkingDirectory)) { WorkingDirectory = suppOptions.WorkingDirectory; } @@ -1713,8 +1758,8 @@ public static XmlReader OpenXml(string content) namespaceManager.AddNamespace(string.Empty, XmlNamespace); XmlParserContext context = new XmlParserContext(settings.NameTable, namespaceManager, string.Empty, XmlSpace.None); - StringReader stringReader = null; - XmlReader reader = null; + StringReader? stringReader = null; + XmlReader? reader = null; bool success = false; try @@ -1756,7 +1801,7 @@ public static object Deserialize(XmlSerializer serializer, XmlReader reader) { try { - return serializer.Deserialize(reader); + return serializer.Deserialize(reader) ?? throw new InvalidLaunchOptionsException(MICoreResources.Error_UnknownLaunchOptions); } catch (InvalidOperationException outerException) { @@ -1812,11 +1857,11 @@ private IEnumerable GetSOLibSearchPathCandidates() } } - if (!string.IsNullOrEmpty(_additionalSOLibSearchPath)) + if (!IsNullOrEmpty(_additionalSOLibSearchPath)) { foreach (string directory in _additionalSOLibSearchPath.Split(';')) { - if (string.IsNullOrWhiteSpace(directory)) + if (IsNullOrWhiteSpace(directory)) continue; // To make sure that all directory names are in a canonical form, if there are any trailing slashes, remove them @@ -1830,7 +1875,7 @@ private IEnumerable GetSOLibSearchPathCandidates() } } - internal static List GetEnvironmentEntries(Xml.LaunchOptions.EnvironmentEntry[] entries) + internal static List GetEnvironmentEntries(Xml.LaunchOptions.EnvironmentEntry[]? entries) { List envList = new List(); if (entries != null) @@ -1843,7 +1888,7 @@ internal static List GetEnvironmentEntries(Xml.LaunchOptions.E return envList; } - internal static List GetEnvironmentEntries(List entries) + internal static List GetEnvironmentEntries(List? entries) { List envList = new List(); if (entries != null) @@ -1860,7 +1905,7 @@ protected void InitializeCommonOptions(Json.LaunchOptions.BaseOptions options) { this.ExePath = options.Program; - if (this.TargetArchitecture == TargetArchitecture.Unknown && !String.IsNullOrWhiteSpace(options.TargetArchitecture)) + if (this.TargetArchitecture == TargetArchitecture.Unknown && !IsNullOrWhiteSpace(options.TargetArchitecture)) { this.TargetArchitecture = ConvertTargetArchitectureAttribute(options.TargetArchitecture); } @@ -1871,7 +1916,7 @@ protected void InitializeCommonOptions(Json.LaunchOptions.BaseOptions options) } this.ShowDisplayString = options.ShowDisplayString.GetValueOrDefault(false); - this.AdditionalSOLibSearchPath = String.IsNullOrEmpty(this.AdditionalSOLibSearchPath) ? + this.AdditionalSOLibSearchPath = IsNullOrEmpty(this.AdditionalSOLibSearchPath) ? options.AdditionalSOLibSearchPath : String.Concat(this.AdditionalSOLibSearchPath, ";", options.AdditionalSOLibSearchPath); @@ -1884,7 +1929,7 @@ protected void InitializeCommonOptions(Json.LaunchOptions.BaseOptions options) { SymbolInfoLoadAll = options.SymbolLoadInfo.LoadAll.GetValueOrDefault(true); - if (!string.IsNullOrWhiteSpace(options.SymbolLoadInfo.ExceptionList)) + if (!IsNullOrWhiteSpace(options.SymbolLoadInfo.ExceptionList)) { if (DebuggerMIMode == MIMode.Lldb) { @@ -1900,8 +1945,8 @@ protected void InitializeCommonOptions(Json.LaunchOptions.BaseOptions options) } } - this.SetupCommands = LaunchCommand.CreateCollection(options.SetupCommands); - this.PostRemoteConnectCommands = LaunchCommand.CreateCollection(options.PostRemoteConnectCommands); + this.SetupCommands = LaunchCommand.CreateCollection(options.SetupCommands ?? new List()); + this.PostRemoteConnectCommands = LaunchCommand.CreateCollection(options.PostRemoteConnectCommands ?? new List()); this.RequireHardwareBreakpoints = options.HardwareBreakpointInfo?.Require ?? false; this.HardwareBreakpointLimit = options.HardwareBreakpointInfo?.Limit ?? 0; @@ -1922,7 +1967,7 @@ protected void InitializeCommonOptions(Xml.LaunchOptions.BaseLaunchOptions sourc if (this.ExePath == null) { string exePath = source.ExePath; - if (!string.IsNullOrWhiteSpace(exePath)) + if (!IsNullOrWhiteSpace(exePath)) { this.ExePath = exePath; } @@ -1935,13 +1980,13 @@ protected void InitializeCommonOptions(Xml.LaunchOptions.BaseLaunchOptions sourc this.DebuggerMIMode = ConvertMIModeAttribute(source.MIMode); - if (string.IsNullOrEmpty(this.ExeArguments)) + if (IsNullOrEmpty(this.ExeArguments)) this.ExeArguments = source.ExeArguments; - if (string.IsNullOrEmpty(this.WorkingDirectory)) + if (IsNullOrEmpty(this.WorkingDirectory)) this.WorkingDirectory = source.WorkingDirectory; - if (!string.IsNullOrEmpty(source.VisualizerFile)) + if (!IsNullOrEmpty(source.VisualizerFile)) this.VisualizerFiles.Add(source.VisualizerFile); this.ShowDisplayString = source.ShowDisplayString; @@ -1965,14 +2010,14 @@ protected void InitializeCommonOptions(Xml.LaunchOptions.BaseLaunchOptions sourc this.LaunchCompleteCommand = (LaunchCompleteCommand)source.LaunchCompleteCommand; string additionalSOLibSearchPath = source.AdditionalSOLibSearchPath; - if (!string.IsNullOrEmpty(additionalSOLibSearchPath)) + if (!IsNullOrEmpty(additionalSOLibSearchPath)) { - if (string.IsNullOrEmpty(this.AdditionalSOLibSearchPath)) + if (IsNullOrEmpty(this.AdditionalSOLibSearchPath)) this.AdditionalSOLibSearchPath = additionalSOLibSearchPath; else this.AdditionalSOLibSearchPath = string.Concat(this.AdditionalSOLibSearchPath, ";", additionalSOLibSearchPath); } - if (string.IsNullOrEmpty(this.AbsolutePrefixSOLibSearchPath)) + if (IsNullOrEmpty(this.AbsolutePrefixSOLibSearchPath)) this.AbsolutePrefixSOLibSearchPath = source.AbsolutePrefixSOLibSearchPath; if (source.DebugChildProcessesSpecified) @@ -1988,14 +2033,14 @@ protected void InitializeCommonOptions(Xml.LaunchOptions.BaseLaunchOptions sourc this.CoreDumpPath = source.CoreDumpPath; // Ensure that CoreDumpPath and ProcessId are not specified at the same time - if (!String.IsNullOrEmpty(source.CoreDumpPath) && source.ProcessIdSpecified) + if (!IsNullOrEmpty(source.CoreDumpPath) && source.ProcessIdSpecified) throw new InvalidLaunchOptionsException(String.Format(CultureInfo.InvariantCulture, MICoreResources.Error_CannotSpecifyBoth, nameof(source.CoreDumpPath), nameof(source.ProcessId))); if (source.SymbolLoadInfo != null) { SymbolInfoLoadAll = source.SymbolLoadInfo.LoadAllSpecified ? source.SymbolLoadInfo.LoadAll : true; - if (DebuggerMIMode == MIMode.Lldb && !string.IsNullOrWhiteSpace(source.SymbolLoadInfo.ExceptionList)) + if (DebuggerMIMode == MIMode.Lldb && !IsNullOrWhiteSpace(source.SymbolLoadInfo.ExceptionList)) { throw new InvalidLaunchOptionsException(String.Format(CultureInfo.InvariantCulture, MICoreResources.Error_OptionNotSupported, nameof(source.SymbolLoadInfo.ExceptionList), nameof(MIMode.Lldb))); } @@ -2012,7 +2057,7 @@ protected void InitializeCommonOptions(Xml.LaunchOptions.BaseLaunchOptions sourc this.Environment = new ReadOnlyCollection(GetEnvironmentEntries(source.Environment)); } - private static List TryAddWindowsDebuggeeConsoleRedirection(List arguments) + private static List? TryAddWindowsDebuggeeConsoleRedirection(List? arguments) { if (PlatformUtilities.IsWindows()) // Only do this on Windows { @@ -2025,7 +2070,7 @@ private static List TryAddWindowsDebuggeeConsoleRedirection(List foreach (string rawArgument in arguments) { // Skip on null or blank arguments. - if (string.IsNullOrWhiteSpace(rawArgument)) + if (IsNullOrWhiteSpace(rawArgument)) { continue; } @@ -2049,7 +2094,7 @@ private static List TryAddWindowsDebuggeeConsoleRedirection(List // If one (or more) are not redirected, then add redirection if (!stdInRedirected || !stdOutRedirected || !stdErrRedirected) { - int argLength = arguments.Count; + int argLength = arguments?.Count ?? 0; List argList = new List(argLength + 3); if (arguments != null) { @@ -2082,12 +2127,12 @@ public void InitializeLaunchOptions(Json.LaunchOptions.LaunchOptions launch) { this.DebuggerMIMode = ConvertMIModeString(RequireAttribute(launch.MIMode, nameof(launch.MIMode))); - List args = launch.Args; + List? args = launch.Args; if (Host.GetHostUIIdentifier() == HostUIIdentifier.VSCode && HostRunInTerminal.IsRunInTerminalAvailable() && !launch.ExternalConsole.GetValueOrDefault(false) && - string.IsNullOrEmpty(launch.CoreDumpPath) && + IsNullOrEmpty(launch.CoreDumpPath) && !launch.AvoidWindowsConsoleRedirection.GetValueOrDefault(false) && !(this is PipeLaunchOptions)) // Make sure we are not doing a PipeLaunch { @@ -2099,7 +2144,7 @@ public void InitializeLaunchOptions(Json.LaunchOptions.LaunchOptions launch) this.CoreDumpPath = launch.CoreDumpPath; - if (launch.CustomLaunchSetupCommands.Any()) + if (launch.CustomLaunchSetupCommands != null && launch.CustomLaunchSetupCommands.Any()) { this.CustomLaunchSetupCommands = LaunchCommand.CreateCollection(launch.CustomLaunchSetupCommands); } @@ -2119,9 +2164,9 @@ public void InitializeAttachOptions(Json.LaunchOptions.AttachOptions attach) this.ProcessId = attach.ProcessId; } - public static string RequireAttribute(string attributeValue, string attributeName) + public static string RequireAttribute(string? attributeValue, string attributeName) { - if (string.IsNullOrWhiteSpace(attributeValue)) + if (IsNullOrWhiteSpace(attributeValue)) throw new InvalidLaunchOptionsException(string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_MissingAttribute, attributeName)); return attributeValue; @@ -2137,17 +2182,18 @@ public static int RequirePortAttribute(int attributeValue, string attributeName) return attributeValue; } - private static LaunchOptions ExecuteLauncher(HostConfigurationStore configStore, Guid clsidLauncher, string exePath, string args, string dir, object launcherXmlOptions, IDeviceAppLauncherEventCallback eventCallback, TargetEngine targetEngine, Logger logger) + private static LaunchOptions ExecuteLauncher(HostConfigurationStore configStore, Guid clsidLauncher, string exePath, string? args, string? dir, object launcherXmlOptions, IDeviceAppLauncherEventCallback eventCallback, TargetEngine targetEngine, Logger? logger) { - var deviceAppLauncher = (IPlatformAppLauncher)HostLoader.VsCoCreateManagedObject(configStore, clsidLauncher); + var deviceAppLauncher = HostLoader.VsCoCreateManagedObject(configStore, clsidLauncher) as IPlatformAppLauncher; if (deviceAppLauncher == null) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_LauncherNotFound, clsidLauncher.ToString("B"))); } + return ExecuteLauncher(configStore, deviceAppLauncher, exePath, args, dir, launcherXmlOptions, eventCallback, targetEngine, logger); } - private static LaunchOptions ExecuteLauncher(HostConfigurationStore configStore, IPlatformAppLauncher deviceAppLauncher, string exePath, string args, string dir, object launcherOptions, IDeviceAppLauncherEventCallback eventCallback, TargetEngine targetEngine, Logger logger) + private static LaunchOptions ExecuteLauncher(HostConfigurationStore configStore, IPlatformAppLauncher deviceAppLauncher, string exePath, string? args, string? dir, object launcherOptions, IDeviceAppLauncherEventCallback eventCallback, TargetEngine targetEngine, Logger? logger) { bool success = false; @@ -2181,7 +2227,7 @@ private static LaunchOptions ExecuteLauncher(HostConfigurationStore configStore, private static XmlSerializer GetXmlSerializer(Type type) { - Assembly serializationAssembly = s_serializationAssembly.Value; + Assembly? serializationAssembly = s_serializationAssembly.Value; if (serializationAssembly == null) { return new XmlSerializer(type); @@ -2189,8 +2235,8 @@ private static XmlSerializer GetXmlSerializer(Type type) else { // NOTE: You can look at MIEngine\src\MICore\obj\Debug\sgen\.cs to see the source code for this assembly. - Type serializerType = serializationAssembly.GetType("Microsoft.Xml.Serialization.GeneratedAssembly." + type.Name + "Serializer"); - ConstructorInfo constructor = serializerType?.GetConstructor(new Type[0]); + Type? serializerType = serializationAssembly.GetType("Microsoft.Xml.Serialization.GeneratedAssembly." + type.Name + "Serializer"); + ConstructorInfo? constructor = serializerType?.GetConstructor(new Type[0]); if (constructor == null) { throw new Exception(string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_UnableToLoadSerializer, type.Name)); @@ -2201,17 +2247,17 @@ private static XmlSerializer GetXmlSerializer(Type type) } } - private static Assembly LoadSerializationAssembly() + private static Assembly? LoadSerializationAssembly() { // This code looks to see if we have sgen-created XmlSerializers assembly next to this dll, which will be true // when the MIEngine is running in Visual Studio. If so, it loads it, so that we can get the performance advantages // of a static XmlSerializers assembly. Otherwise we return null, and we will use a dynamic deserializer. string thisModulePath = typeof(LaunchOptions).GetTypeInfo().Assembly.ManifestModule.FullyQualifiedName; - string thisModuleDir = Path.GetDirectoryName(thisModulePath); + string thisModuleDir = Path.GetDirectoryName(thisModulePath) ?? string.Empty; string thisModuleName = Path.GetFileNameWithoutExtension(thisModulePath); string serializerAssemblyPath = Path.Combine(thisModuleDir, thisModuleName + ".XmlSerializers.dll"); - string thisModuleVersion = typeof(LaunchOptions).GetTypeInfo().Assembly.GetName().Version.ToString(); + string thisModuleVersion = typeof(LaunchOptions).GetTypeInfo().Assembly.GetName().Version?.ToString() ?? string.Empty; if (!File.Exists(serializerAssemblyPath)) return null; @@ -2314,9 +2360,9 @@ public static MIMode ConvertMIModeAttribute(Xml.LaunchOptions.MIMode source) return (MIMode)source; } - protected static string ParseArguments(IEnumerable arguments, bool quoteArguments = true) + protected static string ParseArguments(IEnumerable? arguments, bool quoteArguments = true) { - if (arguments.Any()) + if (arguments != null && arguments.Any()) { StringBuilder stringBuilder = new StringBuilder(); foreach (string arg in arguments) @@ -2339,7 +2385,7 @@ protected static string QuoteArgument(string arg) // Quote if: // 1. string is null or empty and convert to a quoted empty string. // 2. Its not quoted and it has an argument seperator. - if (string.IsNullOrEmpty(arg) || (arg[0] != '"' && arg.IndexOfAny(s_ARGUMENT_SEPARATORS) >= 0)) + if (IsNullOrEmpty(arg) || (arg[0] != '"' && arg.IndexOfAny(s_ARGUMENT_SEPARATORS) >= 0)) { return '"' + arg + '"'; } @@ -2372,7 +2418,7 @@ public interface IPlatformAppLauncher : IDisposable /// [Optional] Working directory of the executable provided in the VsDebugTargetInfo by the project system. Some launchers may ignore this. /// [Required] Deserialized XML options structure or, when using json options, a JObject /// Indicates the type of debugging being done. - void SetLaunchOptions(string exePath, string args, string dir, object launcherOptions, TargetEngine targetEngine); + void SetLaunchOptions(string exePath, string? args, string? dir, object launcherOptions, TargetEngine targetEngine); /// /// Does whatever steps are necessary to setup for debugging. On Android this will include launching diff --git a/src/MICore/Logger.cs b/src/MICore/Logger.cs index 361d34722..0fb9571ed 100644 --- a/src/MICore/Logger.cs +++ b/src/MICore/Logger.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Runtime.CompilerServices; @@ -26,11 +25,11 @@ public class Logger /// /// Optional logger to get engine diagnostics logs /// - private ILogChannel EngineLogger => HostLogger.GetEngineLogChannel(); + private ILogChannel? EngineLogger => HostLogger.GetEngineLogChannel(); /// /// Optional logger to get natvis diagnostics logs /// - public ILogChannel NatvisLogger => HostLogger.GetNatvisLogChannel(); + public ILogChannel? NatvisLogger => HostLogger.GetNatvisLogChannel(); private static int s_count; private readonly int _id; @@ -38,8 +37,8 @@ public class Logger public class LogInfo { - public string logFile; - public Action logToOutput; + public string? logFile; + public Action? logToOutput; public bool enabled; }; @@ -149,7 +148,7 @@ public void WriteTextBlock(LogLevel level, string prefix, string textBlock) if (HostLogger.IsFeedbackLogEnabled) { - HostLogger.WriteFeedbackLog((!string.IsNullOrEmpty(prefix) ? prefix : string.Empty) + textBlock); + HostLogger.WriteFeedbackLog((!IsNullOrEmpty(prefix) ? prefix : string.Empty) + textBlock); } } @@ -202,7 +201,7 @@ private void WriteTextBlockImpl(LogLevel level, string prefix, string textBlock) if (line == null) break; - if (!string.IsNullOrEmpty(prefix)) + if (!IsNullOrEmpty(prefix)) WriteLineImpl(level, prefix + line); else WriteLineImpl(level, line); diff --git a/src/MICore/MICore.csproj b/src/MICore/MICore.csproj index abdc47283..2e860fcbf 100755 --- a/src/MICore/MICore.csproj +++ b/src/MICore/MICore.csproj @@ -2,6 +2,7 @@ 14.0 + enable {54C33AFA-438D-4932-A2F0-D0F2BB2FADC9} Library Properties @@ -23,6 +24,9 @@ + + Shared\%(Filename).cs + True True diff --git a/src/MICore/MIException.cs b/src/MICore/MIException.cs index 7350d7f4a..a289d9aef 100644 --- a/src/MICore/MIException.cs +++ b/src/MICore/MIException.cs @@ -84,7 +84,7 @@ public override string Message get { string message = string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_UnexpectedMIOutput, _debuggerName, _command); - if (!string.IsNullOrWhiteSpace(_miError)) + if (!IsNullOrWhiteSpace(_miError)) { message = string.Concat(message, " ", _miError); } @@ -105,7 +105,7 @@ public class MIDebuggerInitializeFailedException : Exception public readonly IReadOnlyList OutputLines; private readonly string _debuggerName; private readonly IReadOnlyList _errorLines; - private string _message; + private string? _message; public MIDebuggerInitializeFailedException(string debuggerName, IReadOnlyList errorLines, IReadOnlyList outputLines) { @@ -120,7 +120,7 @@ public override string Message { if (_message == null) { - if (_errorLines.Any(x => !string.IsNullOrWhiteSpace(x))) + if (_errorLines.Any(x => !IsNullOrWhiteSpace(x))) { _message = string.Format(CultureInfo.InvariantCulture, MICoreResources.Error_DebuggerInitializeFailed_StdErr, _debuggerName, string.Join("\r\n", _errorLines)); } diff --git a/src/MICore/MIResults.cs b/src/MICore/MIResults.cs index 0b8e4b9a2..539d2b0f2 100644 --- a/src/MICore/MIResults.cs +++ b/src/MICore/MIResults.cs @@ -6,10 +6,14 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -using System.Diagnostics; using System.Collections; using System.Globalization; +using System.Diagnostics.CodeAnalysis; using Microsoft.DebugEngineHost; +using DebuggerDisplayAttribute = global::System.Diagnostics.DebuggerDisplayAttribute; +using DebuggerTypeProxyAttribute = global::System.Diagnostics.DebuggerTypeProxyAttribute; +using DebuggerBrowsableAttribute = global::System.Diagnostics.DebuggerBrowsableAttribute; +using DebuggerBrowsableState = global::System.Diagnostics.DebuggerBrowsableState; namespace MICore { @@ -39,7 +43,7 @@ public virtual ResultValue Find(string name) throw new MIResultFormatException(name, this); } - public virtual bool TryFind(string name, out ResultValue result) + public virtual bool TryFind(string name, [NotNullWhen(true)] out ResultValue? result) { if (Contains(name)) { @@ -49,7 +53,7 @@ public virtual bool TryFind(string name, out ResultValue result) { result = null; } - return result != null; + return result is not null; } public virtual bool Contains(string name) @@ -100,7 +104,7 @@ public uint FindUint(string name) /// The value of the property or null if it cannot be found public uint? TryFindUint(string name) { - ConstValue c; + ConstValue? c; if (!TryFind(name, out c)) { return null; @@ -147,7 +151,7 @@ public ulong FindAddr(string name) /// The value of the address or null if it can't be found public ulong? TryFindAddr(string name) { - ConstValue c; + ConstValue? c; if (!TryFind(name, out c)) { return null; @@ -175,7 +179,7 @@ public string FindString(string name) public string TryFindString(string name) { - ConstValue c; + ConstValue? c; if (!TryFind(name, out c)) { return string.Empty; @@ -186,14 +190,14 @@ public string TryFindString(string name) public T Find(string name) where T : ResultValue { var c = Find(name); - if (c is T) + if (c is T t) { - return c as T; + return t; } throw new MIResultFormatException(name, this); } - public bool TryFind(string name, out T result) where T : ResultValue + public bool TryFind(string name, [NotNullWhen(true)] out T? result) where T : ResultValue { if (Contains(name)) { @@ -203,12 +207,12 @@ public bool TryFind(string name, out T result) where T : ResultValue { result = null; } - return result != null; + return result is not null; } - public T TryFind(string name) where T : ResultValue + public T? TryFind(string name) where T : ResultValue { - T result; + T? result; if (!TryFind(name, out result)) { return null; @@ -279,7 +283,7 @@ public NamedResultValue[] Content { get { - List values = null; + List? values = null; if (_value is ValueListValue) { @@ -307,7 +311,7 @@ public NamedResultValue[] Content }); } - return values?.ToArray(); + return values?.ToArray() ?? Array.Empty(); } } } @@ -410,7 +414,7 @@ public T[] FindAll(string name) where T : class /// /// The list of names that must be added to the TupleValue. /// The list of names that will be added to the TupleValue if they exist in this TupleValue. - public TupleValue Subset(IEnumerable requiredNames, IEnumerable optionalNames = null) + public TupleValue Subset(IEnumerable requiredNames, IEnumerable? optionalNames = null) { List values = new List(); @@ -422,11 +426,11 @@ public TupleValue Subset(IEnumerable requiredNames, IEnumerable } // Iterate the optional list and add the values of the name exists. - if (null != optionalNames) + if (optionalNames is not null) { foreach (string name in optionalNames) { - ResultValue value; + ResultValue? value; if (this.TryFind(name, out value)) { values.Add(new NamedResultValue(name, value)); @@ -566,7 +570,7 @@ public ResultsTypeProxy(Results results) public readonly ResultClass ResultClass; - public Results(ResultClass resultsClass, List list = null) + public Results(ResultClass resultsClass, List? list = null) : base(list ?? new List()) { ResultClass = resultsClass; @@ -678,7 +682,6 @@ public bool StartsWith(string theString, string pattern) } } - private string _resultString; private Logger Logger { get; set; } public MIResults(Logger logger) @@ -692,8 +695,8 @@ public MIResults(Logger logger) /// public Results ParseCommandOutput(string output) { - _resultString = output.Trim(); - int comma = _resultString.IndexOf(','); + string resultString = output.Trim(); + int comma = resultString.IndexOf(','); Results results; ResultClass resultClass = ResultClass.None; if (comma < 0) @@ -704,22 +707,22 @@ public Results ParseCommandOutput(string output) else { resultClass = ParseResultClass(output.Substring(0, comma)); - Span wholeString = new Span(_resultString); - results = ParseResultList(wholeString.AdvanceTo(comma + 1), resultClass); + Span wholeString = new Span(resultString); + results = ParseResultList(resultString, wholeString.AdvanceTo(comma + 1), resultClass); } return results; } public Results ParseResultList(string listStr, ResultClass resultClass = ResultClass.None) { - _resultString = listStr.Trim(); - return ParseResultList(new Span(_resultString), resultClass); + string resultString = listStr.Trim(); + return ParseResultList(resultString, new Span(resultString), resultClass); } - private Results ParseResultList(Span listStr, ResultClass resultClass = ResultClass.None) + private Results ParseResultList(string resultString, Span listStr, ResultClass resultClass = ResultClass.None) { Span rest; - var list = ParseResultList((Span s, ref int i) => + var list = ParseResultList(resultString, (Span s, ref int i) => { return true; }, (Span s, ref int i) => @@ -735,8 +738,8 @@ private Results ParseResultList(Span listStr, ResultClass resultClass = ResultCl } else { - ParseError("trailing chars", rest); - throw new MIResultFormatException(CreateErrorMessageFromSpan(rest), results); + ParseError(resultString, "trailing chars", rest); + throw new MIResultFormatException(CreateErrorMessageFromSpan(resultString, rest), results); } } @@ -746,7 +749,7 @@ public string ParseCString(string input) { throw new ArgumentNullException(nameof(input)); } - else if (string.IsNullOrEmpty(input)) + else if (IsNullOrEmpty(input)) { return string.Empty; } @@ -756,25 +759,8 @@ public string ParseCString(string input) { return input; } - _resultString = cstr; Span rest; - var s = ParseCString(new Span(cstr), out rest); - return s == null ? string.Empty : s.AsString; - } - - private string ParseCString(Span input) - { - if (input.IsEmpty) - { - return string.Empty; - } - - if (_resultString[input.Start] != '\"') // not a Cstring, just return the string - { - return input.Extract(_resultString); - } - Span rest; - var s = ParseCString(input, out rest); + var s = ParseCString(cstr, new Span(cstr), out rest); return s == null ? string.Empty : s.AsString; } @@ -782,30 +768,26 @@ private string ParseCString(Span input) /// value ==>const | tuple | list /// /// - private ResultValue ParseValue(Span resultStr, out Span rest) + private ResultValue? ParseValue(string resultString, Span resultStr, out Span rest) { - ResultValue value = null; rest = Span.Empty; if (resultStr.IsEmpty) { return null; } - switch (_resultString[resultStr.Start]) + switch (resultString[resultStr.Start]) { case '\"': - value = ParseCString(resultStr, out rest); - break; + return ParseCString(resultString, resultStr, out rest); case '{': - value = ParseTuple(resultStr, out rest); - break; + return ParseTuple(resultString, resultStr, out rest); case '[': - value = ParseList(resultStr, out rest); - break; + return ParseList(resultString, resultStr, out rest); default: - ParseError("unexpected char", resultStr); + ParseError(resultString, "unexpected char", resultStr); break; } - return value; + return null; } /// @@ -816,30 +798,26 @@ private ResultValue ParseValue(Span resultStr, out Span rest) /// value -- const | tuple | tuplelist | list /// /// - private ResultValue ParseResultValue(Span resultStr, out Span rest) + private ResultValue? ParseResultValue(string resultString, Span resultStr, out Span rest) { - ResultValue value = null; rest = Span.Empty; if (resultStr.IsEmpty) { return null; } - switch (_resultString[resultStr.Start]) + switch (resultString[resultStr.Start]) { case '\"': - value = ParseCString(resultStr, out rest); - break; + return ParseCString(resultString, resultStr, out rest); case '{': - value = ParseResultTuple(resultStr, out rest); - break; + return ParseResultTuple(resultString, resultStr, out rest); case '[': - value = ParseList(resultStr, out rest); - break; + return ParseList(resultString, resultStr, out rest); default: - ParseError("unexpected char", resultStr); + ParseError(resultString, "unexpected char", resultStr); break; } - return value; + return null; } /// @@ -853,19 +831,20 @@ private static bool IsValueChar(char c) /// /// result ==> variable "=" value /// + /// the original result string /// trimmed input string /// trimmed remainder after result - private NamedResultValue ParseResult(Span resultStr, out Span rest) + private NamedResultValue? ParseResult(string resultString, Span resultStr, out Span rest) { rest = Span.Empty; - int equals = resultStr.IndexOf(_resultString, '='); + int equals = resultStr.IndexOf(resultString, '='); if (equals < 1) { - ParseError("variable not found", resultStr); + ParseError(resultString, "variable not found", resultStr); return null; } - string name = resultStr.Prefix(equals).Extract(_resultString); - ResultValue value = ParseResultValue(resultStr.Advance(equals + 1), out rest); + string name = resultStr.Prefix(equals).Extract(resultString); + ResultValue? value = ParseResultValue(resultString, resultStr.Advance(equals + 1), out rest); if (value == null) { return null; @@ -890,13 +869,13 @@ private static ResultClass ParseResultClass(string resultClass) } } - private ConstValue ParseCString(Span input, out Span rest) + private ConstValue? ParseCString(string resultString, Span input, out Span rest) { rest = input; StringBuilder output = new StringBuilder(); - if (input.IsEmpty || _resultString[input.Start] != '\"') + if (input.IsEmpty || resultString[input.Start] != '\"') { - ParseError("Cstring expected", input); + ParseError(resultString, "Cstring expected", input); return null; } int i = input.Start + 1; @@ -904,12 +883,12 @@ private ConstValue ParseCString(Span input, out Span rest) for (; i < input.Extent; i++) { - char c = _resultString[i]; + char c = resultString[i]; if (c == '\"') { // closing quote, so we are (probably) done i++; - if ((i < input.Extent) && (_resultString[i] == c)) + if ((i < input.Extent) && (resultString[i] == c)) { // double quotes mean we emit a single quote, and carry on ; @@ -923,7 +902,7 @@ private ConstValue ParseCString(Span input, out Span rest) else if (c == '\\') { // escaped character - c = _resultString[++i]; + c = resultString[++i]; switch (c) { case 'n': c = '\n'; break; @@ -933,11 +912,11 @@ private ConstValue ParseCString(Span input, out Span rest) if (c >= '0' && c <= '3') { i = i - 1; - if (SpanOctalChars(_resultString, ref i, output)) + if (SpanOctalChars(resultString, ref i, output)) { continue; // handled the output of the octal-encoded chars } - c = _resultString[i]; // just emit the '\\' + c = resultString[i]; // just emit the '\\' } break; } @@ -946,7 +925,7 @@ private ConstValue ParseCString(Span input, out Span rest) } if (!endFound) { - ParseError("CString not terminated", input); + ParseError(resultString, "CString not terminated", input); return null; } rest = input.AdvanceTo(i); @@ -1003,14 +982,14 @@ bool SpanOctalChars(string str, ref int i, StringBuilder output) private delegate bool EdgeCondition(Span s, ref int i); - private List ParseResultList(EdgeCondition begin, EdgeCondition end, Span input, out Span rest) + private List? ParseResultList(string resultString, EdgeCondition begin, EdgeCondition end, Span input, out Span rest) { rest = Span.Empty; List list = new List(); int i = input.Start; if (!begin(input, ref i)) { - ParseError("Unexpected opening character", input); + ParseError(resultString, "Unexpected opening character", input); return null; } if (end(input, ref i)) // tuple is empty @@ -1019,20 +998,20 @@ private List ParseResultList(EdgeCondition begin, EdgeConditio return list; } input = input.AdvanceTo(i); - var item = ParseResult(input, out rest); + var item = ParseResult(resultString, input, out rest); if (item == null) { - ParseError("Result expected", input); + ParseError(resultString, "Result expected", input); return null; } list.Add(item); input = rest; - while (!input.IsEmpty && _resultString[input.Start] == ',') + while (!input.IsEmpty && resultString[input.Start] == ',') { - item = ParseResult(input.Advance(1), out rest); + item = ParseResult(resultString, input.Advance(1), out rest); if (item == null) { - ParseError("Result expected", input); + ParseError(resultString, "Result expected", input); return null; } list.Add(item); @@ -1042,7 +1021,7 @@ private List ParseResultList(EdgeCondition begin, EdgeConditio i = input.Start; if (!end(input, ref i)) // tuple is not closed { - ParseError("Unexpected list termination", input); + ParseError(resultString, "Unexpected list termination", input); rest = Span.Empty; return null; } @@ -1050,11 +1029,11 @@ private List ParseResultList(EdgeCondition begin, EdgeConditio return list; } - private List ParseResultList(char begin, char end, Span input, out Span rest) + private List? ParseResultList(string resultString, char begin, char end, Span input, out Span rest) { - return ParseResultList((Span s, ref int i) => + return ParseResultList(resultString, (Span s, ref int i) => { - if (_resultString[i] == begin) + if (resultString[i] == begin) { i++; return true; @@ -1062,7 +1041,7 @@ private List ParseResultList(char begin, char end, Span input, return false; }, (Span s, ref int i) => { - if (i < s.Extent && _resultString[i] == end) + if (i < s.Extent && resultString[i] == end) { i++; return true; @@ -1075,21 +1054,25 @@ private List ParseResultList(char begin, char end, Span input, /// tuple ==> "{}" | "{" result ( "," result )* "}" /// /// if one tuple found a TupleValue, otherwise a ValueListValue of TupleValues - private ResultValue ParseResultTuple(Span input, out Span rest) + private ResultValue? ParseResultTuple(string resultString, Span input, out Span rest) { - var list = ParseResultList('{', '}', input, out rest); + var list = ParseResultList(resultString, '{', '}', input, out rest); if (list == null) { return null; } var tlist = new List(); TupleValue v; - while (rest.StartsWith(_resultString, ",{")) + while (rest.StartsWith(resultString, ",{")) { // a tuple list v = new TupleValue(list); tlist.Add(v); - list = ParseResultList('{', '}', rest.Advance(1), out rest); + list = ParseResultList(resultString, '{', '}', rest.Advance(1), out rest); + if (list == null) + { + return null; + } } v = new TupleValue(list); if (tlist.Count != 0) @@ -1103,9 +1086,9 @@ private ResultValue ParseResultTuple(Span input, out Span rest) /// /// tuple ==> "{}" | "{" result ( "," result )* "}" /// - private TupleValue ParseTuple(Span input, out Span rest) + private TupleValue? ParseTuple(string resultString, Span input, out Span rest) { - var list = ParseResultList('{', '}', input, out rest); + var list = ParseResultList(resultString, '{', '}', input, out rest); if (list == null) { return null; @@ -1116,65 +1099,65 @@ private TupleValue ParseTuple(Span input, out Span rest) /// /// list ==> "[]" | "[" value ( "," value )* "]" | "[" result ( "," result )* "]" /// - private ResultValue ParseList(Span input, out Span rest) + private ResultValue? ParseList(string resultString, Span input, out Span rest) { rest = Span.Empty; - if (_resultString[input.Start] != '[') + if (resultString[input.Start] != '[') { - ParseError("List expected", input); + ParseError(resultString, "List expected", input); return null; } - if (_resultString[input.Start + 1] == ']') // list is empty + if (resultString[input.Start + 1] == ']') // list is empty { rest = input.Advance(2); // eat through the closing brace return new ValueListValue(new List()); } - if (IsValueChar(_resultString[input.Start + 1])) + if (IsValueChar(resultString[input.Start + 1])) { - return ParseValueList(input, out rest); + return ParseValueList(resultString, input, out rest); } else { - return ParseResultList(input, out rest); + return ParseResultList(resultString, input, out rest); } } /// /// list ==> "[" value ( "," value )* "]" /// - private ValueListValue ParseValueList(Span input, out Span rest) + private ValueListValue? ParseValueList(string resultString, Span input, out Span rest) { rest = Span.Empty; List list = new List(); - if (_resultString[input.Start] != '[') + if (resultString[input.Start] != '[') { - ParseError("List expected", input); + ParseError(resultString, "List expected", input); return null; } input = input.Advance(1); - var item = ParseValue(input, out rest); + var item = ParseValue(resultString, input, out rest); if (item == null) { - ParseError("Value expected", input); + ParseError(resultString, "Value expected", input); return null; } list.Add(item); input = rest; - while (!input.IsEmpty && _resultString[input.Start] == ',') + while (!input.IsEmpty && resultString[input.Start] == ',') { - item = ParseValue(input.Advance(1), out rest); + item = ParseValue(resultString, input.Advance(1), out rest); if (item == null) { - ParseError("Value expected", input); + ParseError(resultString, "Value expected", input); return null; } list.Add(item); input = rest; } - if (input.IsEmpty || _resultString[input.Start] != ']') // list is not closed + if (input.IsEmpty || resultString[input.Start] != ']') // list is not closed { - ParseError("List not terminated", input); + ParseError(resultString, "List not terminated", input); rest = Span.Empty; return null; } @@ -1185,9 +1168,9 @@ private ValueListValue ParseValueList(Span input, out Span rest) /// /// list ==> "[" result ( "," result )* "]" /// - private ResultListValue ParseResultList(Span input, out Span rest) + private ResultListValue? ParseResultList(string resultString, Span input, out Span rest) { - var list = ParseResultList('[', ']', input, out rest); + var list = ParseResultList(resultString, '[', ']', input, out rest); if (list == null) { return null; @@ -1195,26 +1178,25 @@ private ResultListValue ParseResultList(Span input, out Span rest) return new ResultListValue(list); } - private void ParseError(string message, Span input) + private void ParseError(string resultString, string message, Span input) { - string result = CreateErrorMessageFromSpan(input); + string result = CreateErrorMessageFromSpan(resultString, input); Debug.Fail(message + ": " + result); Logger?.WriteLine(LogLevel.Error, String.Format(CultureInfo.CurrentCulture, "MI parsing error: {0}: \"{1}\"", message, result)); - } // The amount of characters to send to the UI upon an error. private static int PARSE_ERROR_MSG_LIMIT = 1000; - private string CreateErrorMessageFromSpan(Span input) + private string CreateErrorMessageFromSpan(string resultString, Span input) { if (input.Length > PARSE_ERROR_MSG_LIMIT) { input = new Span(input.Start, PARSE_ERROR_MSG_LIMIT); } - return input.Extract(_resultString); + return input.Extract(resultString); } } } diff --git a/src/MICore/PlatformUtilities.cs b/src/MICore/PlatformUtilities.cs index f833ff689..d5356aa7d 100755 --- a/src/MICore/PlatformUtilities.cs +++ b/src/MICore/PlatformUtilities.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Runtime.InteropServices; +using ProcessStartInfo = global::System.Diagnostics.ProcessStartInfo; namespace MICore { @@ -73,13 +73,13 @@ public static bool IsLinux() } // Abstract API call to add an environment variable to a new process - public static void SetEnvironmentVariable(this ProcessStartInfo processStartInfo, string key, string value) + public static void SetEnvironmentVariable(this ProcessStartInfo processStartInfo, string key, string? value) { processStartInfo.Environment[key] = value; } // Abstract API call to add an environment variable to a new process - public static string GetEnvironmentVariable(this ProcessStartInfo processStartInfo, string key) + public static string? GetEnvironmentVariable(this ProcessStartInfo processStartInfo, string key) { if (processStartInfo.Environment.ContainsKey(key)) return processStartInfo.Environment[key]; @@ -99,7 +99,7 @@ public static string WindowsPathToUnixPath(string windowsPath) public static string PathToHostOSPath(string path) { - if (string.IsNullOrWhiteSpace(path)) + if (IsNullOrWhiteSpace(path)) { return path; } diff --git a/src/MICore/ProcessMonitor.cs b/src/MICore/ProcessMonitor.cs index 3e7acd0c2..35d43cddf 100644 --- a/src/MICore/ProcessMonitor.cs +++ b/src/MICore/ProcessMonitor.cs @@ -10,7 +10,7 @@ public class ProcessMonitor : IDisposable { private readonly TimeSpan _EXIT_POLL_DELTA = TimeSpan.FromMilliseconds(200); private int _processId; - private Timer _exitMonitorTimer; + private Timer? _exitMonitorTimer; public ProcessMonitor(int processId) { @@ -27,19 +27,19 @@ public void Start() _exitMonitorTimer = new Timer(MonitorForExit, null, TimeSpan.FromMilliseconds(0), _EXIT_POLL_DELTA); } - public event EventHandler ProcessExited; + public event EventHandler? ProcessExited; private bool HasExited() { return !UnixUtilities.IsProcessRunning(_processId); } - private void MonitorForExit(object o) + private void MonitorForExit(object? o) { if (HasExited()) { - _exitMonitorTimer.Dispose(); - ProcessExited?.Invoke(this, null); + _exitMonitorTimer?.Dispose(); + ProcessExited?.Invoke(this, EventArgs.Empty); } } diff --git a/src/MICore/RunInTerminalLauncher.cs b/src/MICore/RunInTerminalLauncher.cs index 546b10c0d..848b913f6 100644 --- a/src/MICore/RunInTerminalLauncher.cs +++ b/src/MICore/RunInTerminalLauncher.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Diagnostics; using System.Linq; using Microsoft.DebugEngineHost; @@ -13,9 +12,9 @@ namespace MICore { internal class RunInTerminalLauncher { - private string _title; + private readonly string _title; - private Dictionary _environment; + private readonly Dictionary _environment; /// /// @@ -25,7 +24,7 @@ internal class RunInTerminalLauncher public RunInTerminalLauncher(string title, ReadOnlyCollection envEntries) { _title = title; - _environment = new Dictionary(); + _environment = new Dictionary(); if (envEntries != null && envEntries.Any()) { @@ -41,7 +40,7 @@ public void Launch(List cmdArgs, bool useExternalConsole, Action l { if (HostRunInTerminal.IsRunInTerminalAvailable()) { - HostRunInTerminal.RunInTerminal(_title, string.Empty, useExternalConsole, cmdArgs, new ReadOnlyDictionary(_environment), launchCompleteAction, launchFailureAction); + HostRunInTerminal.RunInTerminal(_title, string.Empty, useExternalConsole, cmdArgs, new ReadOnlyDictionary(_environment), launchCompleteAction, launchFailureAction); } } } diff --git a/src/MICore/Transports/ClientServerTransport.cs b/src/MICore/Transports/ClientServerTransport.cs index 88cee7c06..87fe8a4c9 100644 --- a/src/MICore/Transports/ClientServerTransport.cs +++ b/src/MICore/Transports/ClientServerTransport.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Text; using System.Threading; -using System.Diagnostics; using System.IO; using System.Collections; using System.Text.RegularExpressions; @@ -26,7 +25,7 @@ public ClientServerTransport(ITransport clientTransport, ISignalingTransport ser _serverTransport = serverTransport; } - public void Init(ITransportCallback transportCallback, LaunchOptions options, Logger logger, HostWaitLoop waitLoop = null) + public void Init(ITransportCallback transportCallback, LaunchOptions options, Logger logger, HostWaitLoop? waitLoop = null) { _launchTimeout = ((LocalLaunchOptions)options).ServerLaunchTimeout; _serverTransport.Init(transportCallback, options, logger, waitLoop); diff --git a/src/MICore/Transports/ITransport.cs b/src/MICore/Transports/ITransport.cs index ca377a865..77ae05dea 100644 --- a/src/MICore/Transports/ITransport.cs +++ b/src/MICore/Transports/ITransport.cs @@ -13,7 +13,7 @@ namespace MICore public interface ITransport { - void Init(ITransportCallback transportCallback, LaunchOptions options, Logger logger, HostWaitLoop waitLoop = null); + void Init(ITransportCallback transportCallback, LaunchOptions options, Logger logger, HostWaitLoop? waitLoop = null); void Send(string cmd); void Close(); bool IsClosed { get; } @@ -71,7 +71,7 @@ public interface ITransportCallback /// Fired when either the target process exits or when the stdout stream is closed. /// /// [Optional] exit code from the target process. null if unknown. - void OnDebuggerProcessExit(string exitCode); + void OnDebuggerProcessExit(string? exitCode); /// /// Appends a line of text to the initialization log which is dumped to the output diff --git a/src/MICore/Transports/LocalTransport.cs b/src/MICore/Transports/LocalTransport.cs index 85c3e56bd..5efa8a680 100755 --- a/src/MICore/Transports/LocalTransport.cs +++ b/src/MICore/Transports/LocalTransport.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Text; using System.Threading; -using System.Diagnostics; using System.IO; using System.Collections; using System.Runtime.InteropServices; @@ -21,7 +20,7 @@ public LocalTransport() public override void InitStreams(LaunchOptions options, out StreamReader reader, out StreamWriter writer) { LocalLaunchOptions localOptions = (LocalLaunchOptions)options; - string miDebuggerDir = System.IO.Path.GetDirectoryName(localOptions.MIDebuggerPath); + string miDebuggerDir = System.IO.Path.GetDirectoryName(localOptions.MIDebuggerPath) ?? string.Empty; Process proc = new Process(); proc.StartInfo.FileName = localOptions.MIDebuggerPath; @@ -35,8 +34,8 @@ public override void InitStreams(LaunchOptions options, out StreamReader reader, if (PlatformUtilities.IsWindows() && options.DebuggerMIMode == MIMode.Gdb) { - string path = proc.StartInfo.GetEnvironmentVariable("PATH"); - path = (string.IsNullOrEmpty(path) ? miDebuggerDir : path + ";" + miDebuggerDir); + string? path = proc.StartInfo.GetEnvironmentVariable("PATH"); + path = (IsNullOrEmpty(path) ? miDebuggerDir : path + ";" + miDebuggerDir); proc.StartInfo.SetEnvironmentVariable("PATH", path); } @@ -47,7 +46,7 @@ public override void InitStreams(LaunchOptions options, out StreamReader reader, // Allow to execute custom commands before launching debugger. // For ex., instructing GDB not to break for certain signals - if (options.DebuggerMIMode == MIMode.Gdb && !string.IsNullOrWhiteSpace(options.WorkingDirectory)) + if (options.DebuggerMIMode == MIMode.Gdb && !IsNullOrWhiteSpace(options.WorkingDirectory)) { var gdbInitFile = Path.Combine(options.WorkingDirectory, ".gdbinit"); if (File.Exists(gdbInitFile)) diff --git a/src/MICore/Transports/MockTransport.cs b/src/MICore/Transports/MockTransport.cs index d041fa357..691214edf 100644 --- a/src/MICore/Transports/MockTransport.cs +++ b/src/MICore/Transports/MockTransport.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Text; using System.Threading; -using System.Diagnostics; using System.IO; using Microsoft.DebugEngineHost; @@ -20,12 +19,12 @@ namespace MICore public class MockTransport : ITransport { - private ITransportCallback _callback; - private Thread _thread; - private string _nextCommand; + private ITransportCallback? _callback; + private Thread? _thread; + private string? _nextCommand; private bool _bQuit; - private TextReader _reader; - private AutoResetEvent _commandEvent; + private TextReader? _reader; + private AutoResetEvent? _commandEvent; private string _filename; private int _lineNumber; @@ -34,7 +33,7 @@ public MockTransport(string logfilename) _filename = logfilename; } - public void Init(ITransportCallback transportCallback, LaunchOptions options, Logger logger, HostWaitLoop waitLoop = null) + public void Init(ITransportCallback transportCallback, LaunchOptions options, Logger logger, HostWaitLoop? waitLoop = null) { _bQuit = false; _callback = transportCallback; @@ -47,6 +46,8 @@ public void Init(ITransportCallback transportCallback, LaunchOptions options, Lo public void Send(string cmd) { + if (_commandEvent is null) + throw new InvalidOperationException("MockTransport has not been initialized"); Debug.Assert(_nextCommand == null); _nextCommand = cmd; _commandEvent.Set(); @@ -55,7 +56,7 @@ public void Send(string cmd) public void Close() { _bQuit = true; - if (_thread != Thread.CurrentThread) + if (_thread != null && _thread != Thread.CurrentThread) { _thread.Join(); } @@ -70,6 +71,10 @@ public int DebuggerPid private void TransportLoop() { + Debug.Assert(_reader is not null, "Should be impossible -- TransportLoop cannot run until Init is called"); + Debug.Assert(_commandEvent is not null, "Should be impossible -- TransportLoop cannot run until Init is called"); + Debug.Assert(_callback is not null, "Should be impossible -- TransportLoop cannot run until Init is called"); + _lineNumber = 0; // discard first line @@ -78,14 +83,14 @@ private void TransportLoop() while (!_bQuit) { - string line = _reader.ReadLine(); + string? line = _reader.ReadLine(); if (line == null) { break; } line = line.TrimEnd(); _lineNumber++; - Debug.WriteLine("#{0}:{1}", _lineNumber, line); + Debug.WriteLine($"#{_lineNumber}:{line}"); if (line[0] == '-') { diff --git a/src/MICore/Transports/PipeTransport.cs b/src/MICore/Transports/PipeTransport.cs index c456ac590..a78458578 100644 --- a/src/MICore/Transports/PipeTransport.cs +++ b/src/MICore/Transports/PipeTransport.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Text; using System.Threading; -using System.Diagnostics; using System.IO; using System.Collections; using System.Threading.Tasks; @@ -23,15 +22,15 @@ public class PipeTransport : StreamTransport private static readonly object _lock = new object(); - private Process _process; - private StreamReader _stdErrReader; + private Process? _process; + private StreamReader? _stdErrReader; private int _remainingReaders; private ManualResetEvent _allReadersDone = new ManualResetEvent(false); private bool _killOnClose; private bool _filterStderr; private int _debuggerPid = -1; - private string _pipePath; - private string _cmdArgs; + private string? _pipePath; + private string? _cmdArgs; public PipeTransport(bool killOnClose = false, bool filterStderr = false, bool filterStdout = false) : base(filterStdout) { @@ -108,8 +107,8 @@ public override void InitStreams(LaunchOptions options, out StreamReader reader, { PipeLaunchOptions pipeOptions = (PipeLaunchOptions)options; - string workingDirectory = pipeOptions.PipeCwd; - if (!string.IsNullOrWhiteSpace(workingDirectory)) + string? workingDirectory = pipeOptions.PipeCwd; + if (!IsNullOrWhiteSpace(workingDirectory)) { if (!LocalLaunchOptions.CheckDirectoryPath(workingDirectory)) { @@ -119,14 +118,14 @@ public override void InitStreams(LaunchOptions options, out StreamReader reader, else { workingDirectory = Path.GetDirectoryName(pipeOptions.PipePath); - if (!LocalLaunchOptions.CheckDirectoryPath(workingDirectory)) + if (workingDirectory is null || !LocalLaunchOptions.CheckDirectoryPath(workingDirectory)) { // If provided PipeCwd is not an absolute path, the working directory will be set to null. workingDirectory = null; } } - if (string.IsNullOrWhiteSpace(pipeOptions.PipePath)) + if (IsNullOrWhiteSpace(pipeOptions.PipePath)) { throw new ArgumentException(MICoreResources.Error_EmptyPipePath); } @@ -229,7 +228,7 @@ protected override void OnReadStreamAborted() try { - if (_process.WaitForExit(1000)) + if (_process is not null && _process.WaitForExit(1000)) { // If the pipe process has already exited, or is just about to exit, we want to send the abort event from OnProcessExit // instead of from here since that will have access to stderr @@ -251,7 +250,7 @@ private async void AsyncReadFromStream(StreamReader stream, Action lineH { while (true) { - string line = await stream.ReadLineAsync(); + string? line = await stream.ReadLineAsync(); if (line == null) break; @@ -266,11 +265,13 @@ private async void AsyncReadFromStream(StreamReader stream, Action lineH private async void AsyncReadFromStdError() { + Debug.Assert(_stdErrReader is not null, "Should be impossible. AsyncReadFromStdError started before _stdErrReader was assigned."); + StreamReader stdErrReader = _stdErrReader; try { while (true) { - string line = await _stdErrReader.ReadLineAsync(); + string? line = await stdErrReader.ReadLineAsync(); if (line == null) break; @@ -279,7 +280,7 @@ private async void AsyncReadFromStdError() line = FilterLine(line); } - if (!string.IsNullOrWhiteSpace(line)) + if (!IsNullOrWhiteSpace(line)) { this.Callback.OnStdErrorLine(line); } @@ -304,7 +305,7 @@ private void DecrementReaders() } } - private void OnProcessExit(object sender, EventArgs e) + private void OnProcessExit(object? sender, EventArgs e) { // Wait until 'Init' gets a chance to set m_Reader/m_Writer before sending up the debugger exit event if (_reader == null || _writer == null) @@ -325,7 +326,8 @@ private void OnProcessExit(object sender, EventArgs e) // We are sometimes seeing m_process throw InvalidOperationExceptions by the time we get here. // Attempt to get the real exit code, if we can't, still log the message with unknown exit code. - string exitCode = null; + Debug.Assert(_process is not null, "Should be impossible - OnProcessExit is an event handler registered on the process"); + string? exitCode = null; try { exitCode = string.Format(CultureInfo.InvariantCulture, "{0} (0x{0:X})", _process.ExitCode); @@ -349,9 +351,11 @@ private void OnProcessExit(object sender, EventArgs e) private int WrappedExecuteSyncCommand(string commandDescription, string commandText, int timeout) { + Debug.Assert(_cmdArgs is not null && _pipePath is not null, "Should be impossible -- cannot send commands until Init"); + int exitCode = -1; - string output = null; - string error = null; + string output; + string error; string pipeArgs = PipeLaunchOptions.ReplaceDebuggerCommandToken(_cmdArgs, commandText, true); string fullCommand = string.Format(CultureInfo.InvariantCulture, "{0} {1}", _pipePath, pipeArgs); @@ -375,14 +379,14 @@ private int WrappedExecuteSyncCommand(string commandDescription, string commandT public override int ExecuteSyncCommand(string commandDescription, string commandText, int timeout, out string output, out string error) { - output = null; - error = null; + Debug.Assert(_cmdArgs is not null && _pipePath is not null, "Should be impossible -- cannot send commands until Init"); + int exitCode = -1; - Process proc = new Process(); + using Process proc = new Process(); proc.StartInfo.FileName = _pipePath; proc.StartInfo.Arguments = PipeLaunchOptions.ReplaceDebuggerCommandToken(_cmdArgs, commandText, true); - Logger.WriteLine(LogLevel.Verbose, "Running process {0} {1}", proc.StartInfo.FileName, proc.StartInfo.Arguments); + Logger?.WriteLine(LogLevel.Verbose, "Running process {0} {1}", proc.StartInfo.FileName, proc.StartInfo.Arguments); proc.StartInfo.WorkingDirectory = System.IO.Path.GetDirectoryName(_pipePath); proc.EnableRaisingEvents = false; proc.StartInfo.RedirectStandardInput = false; @@ -391,13 +395,28 @@ public override int ExecuteSyncCommand(string commandDescription, string command proc.StartInfo.UseShellExecute = false; proc.StartInfo.CreateNoWindow = true; proc.Start(); - proc.WaitForExit(timeout); - exitCode = proc.ExitCode; + Task stdOutTask = proc.StandardOutput.ReadToEndAsync(); + Task stdErrTask = proc.StandardError.ReadToEndAsync(); + if (proc.WaitForExit(timeout)) + { + exitCode = proc.ExitCode; + output = stdOutTask.Result; + error = stdErrTask.Result; - output = proc.StandardOutput.ReadToEnd(); - error = proc.StandardError.ReadToEnd(); + return exitCode; + } + else + { + try + { + proc.Kill(); + } + catch + { + } - return exitCode; + throw new TimeoutException(); + } } public override bool CanExecuteCommand() diff --git a/src/MICore/Transports/RunInTerminalTransport.cs b/src/MICore/Transports/RunInTerminalTransport.cs index a9fbb8fc5..ada0b141e 100644 --- a/src/MICore/Transports/RunInTerminalTransport.cs +++ b/src/MICore/Transports/RunInTerminalTransport.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Pipes; @@ -18,16 +17,16 @@ namespace MICore public class RunInTerminalTransport : StreamTransport { private int _debuggerPid; - private StreamReader _pidReader; + private StreamReader? _pidReader; - private ProcessMonitor _shellProcessMonitor; + private ProcessMonitor? _shellProcessMonitor; private CancellationTokenSource _streamReadPidCancellationTokenSource = new CancellationTokenSource(); - private Task _waitForConnection = null; + private Task? _waitForConnection = null; - private StreamWriter _commandStream = null; - private StreamReader _outputStream = null; + private StreamWriter? _commandStream; + private StreamReader? _outputStream; - private StreamReader _errorStream = null; + private StreamReader? _errorStream = null; public override int DebuggerPid { @@ -37,9 +36,9 @@ public override int DebuggerPid } } - public override async void Init(ITransportCallback transportCallback, LaunchOptions options, Logger logger, HostWaitLoop waitLoop = null) + public override async void Init(ITransportCallback transportCallback, LaunchOptions options, Logger logger, HostWaitLoop? waitLoop = null) { - LocalLaunchOptions localOptions = options as LocalLaunchOptions; + LocalLaunchOptions localOptions = (LocalLaunchOptions)options; Encoding encNoBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); @@ -66,7 +65,7 @@ public override async void Init(ITransportCallback transportCallback, LaunchOpti _pidReader = new StreamReader(pidPipe, encNoBom, false, UnixUtilities.StreamBufferSize); string thisModulePath = typeof(RunInTerminalTransport).GetTypeInfo().Assembly.ManifestModule.FullyQualifiedName; - string launchCommand = Path.Combine(Path.GetDirectoryName(thisModulePath), "WindowsDebugLauncher.exe"); + string launchCommand = Path.Combine(Path.GetDirectoryName(thisModulePath) ?? string.Empty, "WindowsDebugLauncher.exe"); if (!File.Exists(launchCommand)) { @@ -112,13 +111,13 @@ public override async void Init(ITransportCallback transportCallback, LaunchOpti string debuggeeDir; if (Path.IsPathRooted(options.ExePath) && File.Exists(options.ExePath)) { - debuggeeDir = Path.GetDirectoryName(options.ExePath); + debuggeeDir = Path.GetDirectoryName(options.ExePath) ?? string.Empty; } else { // If we don't know where the app is, default to HOME, and if we somehow can't get that, go with the root directory. - debuggeeDir = Environment.GetEnvironmentVariable("HOME"); - if (string.IsNullOrEmpty(debuggeeDir)) + debuggeeDir = Environment.GetEnvironmentVariable("HOME") ?? string.Empty; + if (IsNullOrEmpty(debuggeeDir)) debuggeeDir = "/"; } @@ -132,7 +131,7 @@ public override async void Init(ITransportCallback transportCallback, LaunchOpti debuggerCmd, localOptions.GetMiDebuggerArgs()); - logger?.WriteTextBlock(LogLevel.Verbose, "DbgCmd:", launchDebuggerCommand); + logger.WriteTextBlock(LogLevel.Verbose, "DbgCmd:", launchDebuggerCommand); using (FileStream dbgCmdStream = new FileStream(dbgCmdScript, FileMode.CreateNew)) using (StreamWriter dbgCmdWriter = new StreamWriter(dbgCmdStream, encNoBom) { AutoFlush = true }) @@ -176,7 +175,7 @@ public override async void Init(ITransportCallback transportCallback, LaunchOpti throw new InvalidOperationException(error); }, logger); - logger?.WriteLine(LogLevel.Verbose, "Wait for connection completion."); + logger.WriteLine(LogLevel.Verbose, "Wait for connection completion."); if (_waitForConnection != null) { @@ -198,7 +197,7 @@ public override async void Init(ITransportCallback transportCallback, LaunchOpti private static string GetOSXLaunchScript() { string thisModulePath = typeof(RunInTerminalTransport).GetTypeInfo().Assembly.ManifestModule.FullyQualifiedName; - string launchScript = Path.Combine(Path.GetDirectoryName(thisModulePath), "osxlaunchhelper.scpt"); + string launchScript = Path.Combine(Path.GetDirectoryName(thisModulePath) ?? string.Empty, "osxlaunchhelper.scpt"); if (!File.Exists(launchScript)) { string message = string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_InternalFileMissing, launchScript); @@ -214,7 +213,7 @@ private void LogDebuggerErrors() { while (!_streamReadPidCancellationTokenSource.IsCancellationRequested) { - string line = this.GetLineFromStream(_errorStream, _streamReadPidCancellationTokenSource.Token); + string? line = this.GetLineFromStream(_errorStream, _streamReadPidCancellationTokenSource.Token); if (line == null) break; Logger?.WriteTextBlock(LogLevel.Error, "dbgerr:", line); @@ -224,12 +223,13 @@ private void LogDebuggerErrors() public override void InitStreams(LaunchOptions options, out StreamReader reader, out StreamWriter writer) { + Debug.Assert(_commandStream is not null && _outputStream is not null, "Should be impossible -- `Init` should be called before `InitStreams`"); // Mono seems to stop responding when the debugger sends a large response unless we specify a larger buffer here writer = _commandStream; reader = _outputStream; } - private Action debuggerPidCallback; + private Action? debuggerPidCallback; public void RegisterDebuggerPidCallback(Action pidCallback) { debuggerPidCallback = pidCallback; @@ -240,10 +240,12 @@ private void LaunchSuccess(int? pid) if (_pidReader != null) { int shellPid; - Task readShellPidTask = _pidReader.ReadLineAsync(); + Task readShellPidTask = _pidReader.ReadLineAsync(); if (readShellPidTask.Wait(TimeSpan.FromSeconds(10))) { - shellPid = int.Parse(readShellPidTask.Result, CultureInfo.InvariantCulture); + string? shellPidLine = readShellPidTask.Result; + Debug.Assert(shellPidLine is not null, "Should be impossible. Shell pid line was null."); + shellPid = int.Parse(shellPidLine, CultureInfo.InvariantCulture); // Used for testing Logger?.WriteLine(LogLevel.Verbose, string.Concat("ShellPid=", shellPid)); } @@ -268,11 +270,13 @@ private void LaunchSuccess(int? pid) shellProcess.Exited += ShellExited; } - Task readDebuggerPidTask = _pidReader.ReadLineAsync(); + Task readDebuggerPidTask = _pidReader.ReadLineAsync(); try { readDebuggerPidTask.Wait(_streamReadPidCancellationTokenSource.Token); - _debuggerPid = int.Parse(readDebuggerPidTask.Result, CultureInfo.InvariantCulture); + string? debuggerPidLine = readDebuggerPidTask.Result; + Debug.Assert(debuggerPidLine is not null, "Should be impossible. Debugger pid line was null."); + _debuggerPid = int.Parse(debuggerPidLine, CultureInfo.InvariantCulture); } catch (OperationCanceledException) { @@ -289,7 +293,7 @@ private void LaunchSuccess(int? pid) } } - private void ShellExited(object sender, EventArgs e) + private void ShellExited(object? sender, EventArgs e) { if (sender is ProcessMonitor) { diff --git a/src/MICore/Transports/ServerTransport.cs b/src/MICore/Transports/ServerTransport.cs index f68923970..c973f5643 100644 --- a/src/MICore/Transports/ServerTransport.cs +++ b/src/MICore/Transports/ServerTransport.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Text; using System.Threading; -using System.Diagnostics; using System.IO; using System.Collections; using System.Text.RegularExpressions; @@ -15,8 +14,8 @@ namespace MICore { public class ServerTransport : PipeTransport, ISignalingTransport { - private string _startPattern; - public string _messagePrefix; + private string? _startPattern; + public string? _messagePrefix; private bool _started; public ManualResetEvent StartedEvent { get; } @@ -30,7 +29,7 @@ public ServerTransport(bool killOnClose, bool filterStderr = false, bool filterS public override void InitStreams(LaunchOptions options, out StreamReader reader, out StreamWriter writer) { LocalLaunchOptions localOptions = (LocalLaunchOptions)options; - string miDebuggerDir = System.IO.Path.GetDirectoryName(localOptions.MIDebuggerPath); + string miDebuggerDir = System.IO.Path.GetDirectoryName(localOptions.MIDebuggerPath) ?? string.Empty; Process proc = new Process(); proc.StartInfo.FileName = localOptions.DebugServer; @@ -42,9 +41,9 @@ public override void InitStreams(LaunchOptions options, out StreamReader reader, InitProcess(proc, out reader, out writer); } - protected override string FilterLine(string line) + protected override string? FilterLine(string line) { - if (!_started && (String.IsNullOrWhiteSpace(_startPattern) || Regex.IsMatch(line, _startPattern, RegexOptions.None, new TimeSpan(0, 0, 0, 0, 10) /* 10 ms */))) + if (!_started && (IsNullOrWhiteSpace(_startPattern) || Regex.IsMatch(line, _startPattern, RegexOptions.None, new TimeSpan(0, 0, 0, 0, 10) /* 10 ms */))) { _started = true; StartedEvent.Set(); diff --git a/src/MICore/Transports/StreamTransport.cs b/src/MICore/Transports/StreamTransport.cs index 9de2f3c32..d63aa8979 100644 --- a/src/MICore/Transports/StreamTransport.cs +++ b/src/MICore/Transports/StreamTransport.cs @@ -4,7 +4,6 @@ using Microsoft.DebugEngineHost; using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Text; @@ -15,16 +14,16 @@ namespace MICore { public abstract class StreamTransport : ITransport { - private ITransportCallback _callback; - private Thread _thread; + private ITransportCallback? _callback; + private Thread? _thread; private bool _bQuit; private CancellationTokenSource _streamReadCancellationTokenSource = new CancellationTokenSource(); - protected StreamReader _reader; - protected StreamWriter _writer; + protected StreamReader? _reader; + protected StreamWriter? _writer; private bool _filterStdout; private Object _locker = new object(); - protected Logger Logger + protected Logger? Logger { get; private set; } @@ -40,7 +39,7 @@ protected StreamTransport(bool filterStdout) public abstract void InitStreams(LaunchOptions options, out StreamReader reader, out StreamWriter writer); protected virtual string GetThreadName() { return "MI.StreamTransport"; } - public virtual void Init(ITransportCallback transportCallback, LaunchOptions options, Logger logger, HostWaitLoop waitLoop = null) + public virtual void Init(ITransportCallback transportCallback, LaunchOptions options, Logger logger, HostWaitLoop? waitLoop = null) { Logger = logger; _callback = transportCallback; @@ -55,7 +54,7 @@ private void StartThread(string name) _thread.Start(); } - protected virtual string FilterLine(string line) + protected virtual string? FilterLine(string line) { return line; } @@ -66,7 +65,7 @@ private void TransportLoop() { while (!_bQuit) { - string line = GetLine(); + string? line = GetLine(); if (line == null) break; @@ -80,9 +79,9 @@ private void TransportLoop() { line = FilterLine(line); } - if (!String.IsNullOrWhiteSpace(line) && !line.StartsWith("-", StringComparison.Ordinal)) + if (!IsNullOrWhiteSpace(line) && !line.StartsWith("-", StringComparison.Ordinal)) { - _callback.OnStdOutLine(line); + Callback.OnStdOutLine(line); } } catch (ObjectDisposedException) @@ -106,11 +105,14 @@ private void TransportLoop() // If we are shutting down without notice from the debugger (e.g., the terminal // where the debugger was hosted was closed), at this point it's possible that // there is a thread blocked doing a read() syscall. - ForceDisposeStreamReader(_reader); + if (_reader != null) + { + ForceDisposeStreamReader(_reader); + } try { - _writer.Dispose(); + _writer?.Dispose(); _writer = null; } catch @@ -133,7 +135,7 @@ protected virtual void OnReadStreamAborted() { try { - _callback.OnDebuggerProcessExit(null); + Callback.OnDebuggerProcessExit(null); } catch { @@ -142,7 +144,7 @@ protected virtual void OnReadStreamAborted() } protected void Echo(string cmd) { - if (!String.IsNullOrWhiteSpace(cmd)) + if (!IsNullOrWhiteSpace(cmd)) { Logger?.WriteLine(LogLevel.Verbose, "<-" + cmd); Logger?.Flush(); @@ -155,16 +157,17 @@ protected void Echo(string cmd) } } - private string GetLine() + private string? GetLine() { + Debug.Assert(_reader is not null, "Should be impossible - GetLine is only called from the transport loop started after Init"); return GetLineFromStream(_reader, _streamReadCancellationTokenSource.Token); } - protected string GetLineFromStream(StreamReader reader, CancellationToken token) + protected string? GetLineFromStream(StreamReader reader, CancellationToken token) { try { - Task task = reader.ReadLineAsync(); + Task task = reader.ReadLineAsync(); task.Wait(token); return task.Result; } @@ -217,7 +220,11 @@ public bool IsClosed protected ITransportCallback Callback { - get { return _callback; } + get + { + Debug.Assert(_callback is not null, "Should be impossible - Callback is only used after Init completes"); + return _callback; + } } /// diff --git a/src/MICore/Transports/TcpTransport.cs b/src/MICore/Transports/TcpTransport.cs index 72bb93145..398ea9fe5 100755 --- a/src/MICore/Transports/TcpTransport.cs +++ b/src/MICore/Transports/TcpTransport.cs @@ -15,7 +15,7 @@ namespace MICore { public class TcpTransport : StreamTransport { - private TcpClient _client; + private TcpClient? _client; public TcpTransport() { @@ -40,7 +40,7 @@ public override void InitStreams(LaunchOptions options, out StreamReader reader, if (tcpOptions.ServerCertificateValidationCallback == null) { //if no callback specified, accept any certificate - callback = delegate (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) + callback = delegate (object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) { return sslPolicyErrors == SslPolicyErrors.None; }; @@ -48,7 +48,7 @@ public override void InitStreams(LaunchOptions options, out StreamReader reader, else { //else use the callback specified - callback = (object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslPolicyErrors) => tcpOptions.ServerCertificateValidationCallback(sender, certificate, chain, sslPolicyErrors); + callback = (object sender, X509Certificate? certificate, X509Chain? chain, SslPolicyErrors sslPolicyErrors) => tcpOptions.ServerCertificateValidationCallback(sender, certificate, chain, sslPolicyErrors); } var certStore = new X509Store(StoreName.My, StoreLocation.CurrentUser); @@ -83,7 +83,7 @@ public override int DebuggerPid public override void Close() { base.Close(); - ((IDisposable)_client).Dispose(); + ((IDisposable?)_client)?.Dispose(); } public override int ExecuteSyncCommand(string commandDescription, string commandText, int timeout, out string output, out string error) diff --git a/src/MICore/Transports/UnixShellPortTransport.cs b/src/MICore/Transports/UnixShellPortTransport.cs index 95cd72933..22de4e6ed 100644 --- a/src/MICore/Transports/UnixShellPortTransport.cs +++ b/src/MICore/Transports/UnixShellPortTransport.cs @@ -17,20 +17,20 @@ namespace MICore public class UnixShellPortTransport : ITransport, IDebugUnixShellCommandCallback { private readonly object _closeLock = new object(); - private ITransportCallback _callback; - private Logger _logger; - private string _startRemoteDebuggerCommand; - private IDebugUnixShellAsyncCommand _asyncCommand; + private ITransportCallback? _callback; + private Logger? _logger; + private string? _startRemoteDebuggerCommand; + private IDebugUnixShellAsyncCommand? _asyncCommand; private bool _bQuit; private bool _debuggerLaunched = false; - private UnixShellPortLaunchOptions _launchOptions; + private UnixShellPortLaunchOptions? _launchOptions; private const string ErrorPrefix = "Error:"; private class KillCommandCallback: IDebugUnixShellCommandCallback { - private readonly Logger _logger; - public KillCommandCallback(Logger logger) + private readonly Logger? _logger; + public KillCommandCallback(Logger? logger) { this._logger = logger; } @@ -49,7 +49,7 @@ public UnixShellPortTransport() { } - public void Init(ITransportCallback transportCallback, LaunchOptions options, Logger logger, HostWaitLoop waitLoop = null) + public void Init(ITransportCallback transportCallback, LaunchOptions options, Logger logger, HostWaitLoop? waitLoop = null) { _launchOptions = (UnixShellPortLaunchOptions)options; _callback = transportCallback; @@ -68,12 +68,13 @@ public void Close() return; _bQuit = true; - _asyncCommand.Abort(); + _asyncCommand?.Abort(); } } public void Send(string cmd) { + Debug.Assert(_asyncCommand is not null, "Should be impossible - Send is only called by the engine after Init"); _logger?.WriteLine(LogLevel.Verbose, "<-" + cmd); _logger?.Flush(); _asyncCommand.WriteLine(cmd); @@ -94,12 +95,14 @@ bool ITransport.IsClosed void IDebugUnixShellCommandCallback.OnOutputLine(string line) { + Debug.Assert(_callback is not null, "Should be impossible - OnOutputLine is a callback from the command started in Init which sets _callback"); + if (!_debuggerLaunched) { _debuggerLaunched = true; } - if (!string.IsNullOrEmpty(line)) + if (!IsNullOrEmpty(line)) { _callback.OnStdOutLine(line); } @@ -110,6 +113,8 @@ void IDebugUnixShellCommandCallback.OnOutputLine(string line) void IDebugUnixShellCommandCallback.OnExit(string exitCode) { + Debug.Assert(_callback is not null, "Should be impossible - OnExit is a callback from the command started in Init which sets _callback"); + if (!_bQuit) { _callback.AppendToInitializationLog(string.Format(CultureInfo.InvariantCulture, "{0} exited with code {1}.", _startRemoteDebuggerCommand, exitCode ?? "???")); @@ -128,8 +133,9 @@ void IDebugUnixShellCommandCallback.OnExit(string exitCode) public int ExecuteSyncCommand(string commandDescription, string commandText, int timeout, out string output, out string error) { + Debug.Assert(_launchOptions is not null, "Should be impossible - ExecuteSyncCommand is only called during active debugging after Init"); int errorCode = -1; - error = null; // In SSH transport, stderr is printed on stdout. + error = string.Empty; // In SSH transport, stderr is printed on stdout. _launchOptions.UnixPort.ExecuteSyncCommand(commandDescription, commandText, out output, timeout, out errorCode); return errorCode; } @@ -141,6 +147,9 @@ public bool CanExecuteCommand() public bool Interrupt(int pid) { + Debug.Assert(_launchOptions is not null, "Should be impossible - Interrupt is only called during active debugging after Init"); + Debug.Assert(_callback is not null, "Should be impossible - Interrupt is only called during active debugging after Init"); + string killCmd = string.Format(CultureInfo.InvariantCulture, "/bin/sh -c \"kill -5 {0}\"", pid); try diff --git a/src/MICore/UnixUtilities.cs b/src/MICore/UnixUtilities.cs index 36332aeef..ae251487e 100644 --- a/src/MICore/UnixUtilities.cs +++ b/src/MICore/UnixUtilities.cs @@ -4,7 +4,6 @@ using Microsoft.DebugEngineHost; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.IO; using System.Text; @@ -104,7 +103,7 @@ internal static string GetDebuggerCommand(LocalLaunchOptions localOptions) } } - internal static string MakeFifo(string identifier = null, Logger logger = null) + internal static string MakeFifo(string? identifier = null, Logger? logger = null) { string path = Path.Combine(Path.GetTempPath(), Utilities.GetMIEngineTemporaryFilename(identifier)); @@ -200,9 +199,9 @@ public static bool IsBinarySigned(string filePath, Logger logger) return p.ExitCode == 0; } - internal static void OutputNonEmptyString(string str, string prefix, Logger logger) + internal static void OutputNonEmptyString(string? str, string prefix, Logger logger) { - if (!String.IsNullOrWhiteSpace(str) && logger != null) + if (!IsNullOrWhiteSpace(str) && logger != null) { logger.WriteLine(LogLevel.Verbose, prefix + str); } @@ -223,7 +222,7 @@ internal static void KillProcessTree(Process p) ps.StartInfo.RedirectStandardOutput = true; ps.StartInfo.UseShellExecute = false; ps.Start(); - string line; + string? line; List> processAndParent = new List>(); char[] whitespace = new char[] { ' ', '\t' }; while ((line = ps.StandardOutput.ReadLine()) != null) diff --git a/src/MICore/Utilities.cs b/src/MICore/Utilities.cs index 6a418a518..270742437 100644 --- a/src/MICore/Utilities.cs +++ b/src/MICore/Utilities.cs @@ -11,10 +11,10 @@ internal static class Utilities { private const string TempNamePrefix = "Microsoft-MIEngine-"; private const string Separator = "-"; - internal static string GetMIEngineTemporaryFilename(string identifier = null) + internal static string GetMIEngineTemporaryFilename(string? identifier = null) { // add the identifier + separator if the identifier exists - string optionalIdentifier = string.IsNullOrEmpty(identifier) ? string.Empty : identifier + Separator; + string optionalIdentifier = IsNullOrEmpty(identifier) ? string.Empty : identifier + Separator; string filename = String.Concat(TempNamePrefix, optionalIdentifier, Path.GetRandomFileName()); return filename; From a5d90d82d70a67e5b32bf49609239d8ba3b11eb8 Mon Sep 17 00:00:00 2001 From: SachinM123 <114114188+SachinM123@users.noreply.github.com> Date: Thu, 2 Jul 2026 09:49:56 -0700 Subject: [PATCH 2/4] Fix race condition monitor thread in RegistryMonitor (#1604) This PR fixes a race condition in Microsoft.DebugEngineHost.RegisteryMonitor where if `Stop` was called before the monitor thread started, it would never shutdown. This fixes https://github.com/microsoft/MIEngine/issues/1593 --- src/DebugEngineHost/RegistryMonitor.cs | 31 +++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/DebugEngineHost/RegistryMonitor.cs b/src/DebugEngineHost/RegistryMonitor.cs index d3e53410f..2fdc26e34 100644 --- a/src/DebugEngineHost/RegistryMonitor.cs +++ b/src/DebugEngineHost/RegistryMonitor.cs @@ -72,6 +72,15 @@ public RegistryMonitor(HostConfigurationSection section, bool watchSubtree, ILog public void Start() { + lock (_stopLock) + { + if (_stoppedEvent != null) + { + throw new InvalidOperationException("RegistryMonitor already started."); + } + _isStopped = false; + _stoppedEvent = new AutoResetEvent(false); + } Thread registryMonitor = new Thread(Monitor); registryMonitor.IsBackground = true; registryMonitor.Name = "Microsoft.DebugEngineHost.RegistryMonitor"; @@ -97,7 +106,15 @@ private void Monitor() bool stopped = false; try { - _stoppedEvent = new AutoResetEvent(false); + // Ensure stop isn't requested before we create/wait on events. + lock (_stopLock) + { + if (_isStopped) + { + return; + } + } + using (AutoResetEvent registryChangedEvent = new AutoResetEvent(false)) { IntPtr handle = registryChangedEvent.SafeWaitHandle.DangerousGetHandle(); @@ -111,7 +128,9 @@ private void Monitor() { while (!stopped) { - int waitResult = WaitHandle.WaitAny(new WaitHandle[] { _stoppedEvent, registryChangedEvent }); + AutoResetEvent? stoppedEvent = _stoppedEvent; + Debug.Assert(stoppedEvent is not null, "Should be impossible - this code can only run after `Start` is called."); + int waitResult = WaitHandle.WaitAny(new WaitHandle[] { stoppedEvent, registryChangedEvent }); if (waitResult == 0) { @@ -133,9 +152,11 @@ private void Monitor() } finally { - _stoppedEvent?.Dispose(); - _stoppedEvent = null; - + lock (_stopLock) + { + _stoppedEvent?.Dispose(); + _stoppedEvent = null; + } _section.Dispose(); } } From bb0255229398101c7bcf6cc97edca6c87261dae5 Mon Sep 17 00:00:00 2001 From: tieo <65707274+tieo@users.noreply.github.com> Date: Mon, 6 Jul 2026 20:44:21 +0200 Subject: [PATCH 3/4] Don't expose a memory reference for non-pointer evaluation results (#1601) Gate the memoryReference on pointer and array types in OpenDebugAD7 A non-pointer scalar shown in a data tip or the Variables view carried a memoryReference, so VS Code rendered the "view binary data" icon and navigated to an address equal to the value (hovering a uint32_t of 1 opened a memory view at 0x1). The earlier approach gated AD7Property.GetMemoryContext in the engine, but that method is shared with Visual Studio, where the Memory and Disassembly windows resolve a typed address expression through it. Restricting it there breaks entering a scalar or address expression into those windows. Move the restriction to the DAP layer: in AD7Utils.GetMemoryReferenceFromIDebugProperty, emit a memoryReference only when the property type is a pointer or an array. GetMemoryContext is left unchanged, so the Visual Studio memory and disassembly navigation keep working, while VS Code no longer offers a memory view for scalars. VS Code has no free-form address entry, so gating the reference removes no entry point there. --- src/OpenDebugAD7/AD7Utils.cs | 44 ++++++++++++++++++++++++++++++++++-- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/src/OpenDebugAD7/AD7Utils.cs b/src/OpenDebugAD7/AD7Utils.cs index 6933256d1..32ef2a376 100644 --- a/src/OpenDebugAD7/AD7Utils.cs +++ b/src/OpenDebugAD7/AD7Utils.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All rights reserved. +// Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. using System; @@ -20,7 +20,23 @@ public static bool IsAnnotatedFrame(ref FRAMEINFO frameInfo) public static string GetMemoryReferenceFromIDebugProperty(IDebugProperty2 property) { - if (property != null && property.GetMemoryContext(out IDebugMemoryContext2 memoryContext) == HRConstants.S_OK) + if (property == null) + { + return null; + } + + // Only a pointer or an array holds an address a memoryReference can point at. + // For any other type the value is not an address, so a memoryReference would + // make the client show a memory view that navigates to the value itself. The + // engine's GetMemoryContext still resolves an address for any type, which the + // Visual Studio Memory and Disassembly windows rely on; the restriction here + // applies only to the reference reported over DAP. + if (!IsPointerOrArray(property)) + { + return null; + } + + if (property.GetMemoryContext(out IDebugMemoryContext2 memoryContext) == HRConstants.S_OK) { CONTEXT_INFO[] contextInfo = new CONTEXT_INFO[1]; if (memoryContext.GetInfo(enum_CONTEXT_INFO_FIELDS.CIF_ADDRESS, contextInfo) == HRConstants.S_OK) @@ -34,5 +50,29 @@ public static string GetMemoryReferenceFromIDebugProperty(IDebugProperty2 proper return null; } + + private static bool IsPointerOrArray(IDebugProperty2 property) + { + DEBUG_PROPERTY_INFO[] propertyInfo = new DEBUG_PROPERTY_INFO[1]; + if (property.GetPropertyInfo(enum_DEBUGPROP_INFO_FLAGS.DEBUGPROP_INFO_TYPE, Constants.EvaluationRadix, Constants.EvaluationTimeout, null, 0, propertyInfo) != HRConstants.S_OK) + { + return false; + } + + if (!propertyInfo[0].dwFields.HasFlag(enum_DEBUGPROP_INFO_FLAGS.DEBUGPROP_INFO_TYPE)) + { + return false; + } + + string typeName = propertyInfo[0].bstrType; + if (string.IsNullOrEmpty(typeName)) + { + return false; + } + + typeName = typeName.TrimEnd(); + return typeName.EndsWith("*", StringComparison.Ordinal) // pointer, e.g. "int *" + || typeName.EndsWith("]", StringComparison.Ordinal); // array, e.g. "int [10]" + } } } From 2d1de85e3d1ff89b5c7e998378ec17b8deee8e61 Mon Sep 17 00:00:00 2001 From: Andrew Wang Date: Fri, 10 Jul 2026 10:05:45 -0700 Subject: [PATCH 4/4] Merge pull request #1608 from microsoft/dev/wardengnaw/updateMinor8 Increment MinorVersion from 8 to 10 --- build/version.settings.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/version.settings.targets b/build/version.settings.targets index 53ed9072f..cad25b7d9 100644 --- a/build/version.settings.targets +++ b/build/version.settings.targets @@ -3,7 +3,7 @@ 18 - 8 + 10 2025