From af0d2ee60cb4b10cbd23e5af8479ef088131ee3e Mon Sep 17 00:00:00 2001 From: Gregg Miskelly Date: Mon, 22 Jun 2026 13:10:13 -0700 Subject: [PATCH] Nullablitiy for MICore --- src/MICore/Checksum.cs | 4 +- .../CommandFactories/MICommandFactory.cs | 27 +- src/MICore/CommandFactories/gdb.cs | 16 +- src/MICore/CommandFactories/lldb.cs | 14 +- src/MICore/CommandLock.cs | 21 +- src/MICore/Debugger.cs | 132 +++---- src/MICore/DebuggerDisposedException.cs | 6 +- src/MICore/ExceptionHelper.cs | 5 +- src/MICore/GlobalUsings.cs | 5 + src/MICore/IncludeExcludeList.cs | 10 +- src/MICore/JsonLaunchOptions.cs | 192 ++++----- src/MICore/LaunchCommand.cs | 19 +- src/MICore/LaunchOptions.cs | 369 ++++++++++-------- src/MICore/Logger.cs | 18 +- src/MICore/MICore.csproj | 4 + src/MICore/MIException.cs | 6 +- src/MICore/MIResults.cs | 263 +++++-------- src/MICore/PlatformUtilities.cs | 6 +- src/MICore/ProcessMonitor.cs | 10 +- src/MICore/RunInTerminalLauncher.cs | 1 - .../Transports/ClientServerTransport.cs | 5 +- src/MICore/Transports/ITransport.cs | 6 +- src/MICore/Transports/LocalTransport.cs | 9 +- src/MICore/Transports/MockTransport.cs | 27 +- src/MICore/Transports/PipeTransport.cs | 44 ++- .../Transports/RunInTerminalTransport.cs | 52 +-- src/MICore/Transports/ServerTransport.cs | 11 +- src/MICore/Transports/StreamTransport.cs | 47 ++- src/MICore/Transports/TcpTransport.cs | 10 +- .../Transports/UnixShellPortTransport.cs | 31 +- src/MICore/UnixUtilities.cs | 9 +- src/MICore/Utilities.cs | 4 +- .../Engine.Impl/DebuggedProcess.cs | 4 +- 33 files changed, 715 insertions(+), 672 deletions(-) create mode 100644 src/MICore/GlobalUsings.cs 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 07de24092..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; @@ -48,7 +47,7 @@ public enum AsyncBreakSignal public abstract class MICommandFactory { - protected Debugger _debugger; + protected readonly Debugger _debugger; public MIMode Mode { get; private set; } @@ -56,6 +55,11 @@ public abstract class MICommandFactory internal int MajorVersion { get; set; } + protected MICommandFactory(Debugger debugger) + { + _debugger = debugger; + } + public static MICommandFactory GetInstance(MIMode mode, Debugger debugger) { MICommandFactory commandFactory; @@ -63,21 +67,20 @@ public static MICommandFactory GetInstance(MIMode mode, Debugger debugger) switch (mode) { case MIMode.Gdb: - commandFactory = new GdbMICommandFactory(); + commandFactory = new GdbMICommandFactory(debugger); break; case MIMode.Lldb: - commandFactory = new LlldbMICommandFactory(); + commandFactory = new LlldbMICommandFactory(debugger); break; default: throw new ArgumentException(null, nameof(mode)); } - commandFactory._debugger = debugger; commandFactory.Mode = mode; commandFactory.Radix = 10; 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', '\"' }; @@ -479,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); @@ -529,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) @@ -559,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; } @@ -628,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(); } @@ -704,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 83100ab3e..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; @@ -19,6 +18,11 @@ internal class GdbMICommandFactory : MICommandFactory private int _currentThreadId = 0; private uint _currentFrameLevel = 0; + public GdbMICommandFactory(Debugger debugger) + : base(debugger) + { + } + public override string Name { get { return "GDB"; } @@ -52,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 { @@ -98,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 { @@ -188,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; @@ -276,7 +280,7 @@ public override TargetArchitecture ParseTargetArchitectureResult(string result) { while (true) { - string resultLine = stringReader.ReadLine(); + string? resultLine = stringReader.ReadLine(); if (resultLine == null) break; @@ -322,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 b430f3257..b55624039 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; @@ -16,6 +15,11 @@ namespace MICore { internal class LlldbMICommandFactory : MICommandFactory { + public LlldbMICommandFactory(Debugger debugger) + : base(debugger) + { + } + public override string Name { get { return "LLDB"; } @@ -111,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.FromResult(null); } public override string GetTargetArchitectureCommand() @@ -131,7 +135,7 @@ public override TargetArchitecture ParseTargetArchitectureResult(string result) { while (true) { - string resultLine = stringReader.ReadLine(); + string? resultLine = stringReader.ReadLine(); if (resultLine == null) break; @@ -211,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 710d6d70c..77ec63868 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; @@ -72,7 +71,7 @@ public bool IsClosed public uint MaxInstructionSize { get; private set; } public bool Is64BitArch { get; private set; } public CommandLock CommandLock { get { return _commandLock; } } - public MICommandFactory MICommandFactory { get; protected set; } + public MICommandFactory MICommandFactory { get; } public Logger Logger { private set; get; } [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes")] @@ -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,25 +122,25 @@ public StoppingEventArgs(Results results, BreakRequest asyncRequest = BreakReque { } } - private ITransport _transport; - private CommandLock _commandLock = new CommandLock(); + private ITransport? _transport; + private readonly CommandLock _commandLock = new CommandLock(); /// /// The last command we sent over the transport. This includes both the command name and arguments. /// - private string _lastCommandText; + private string _lastCommandText = string.Empty; private uint _lastCommandId; /// /// 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 @@ -169,6 +168,7 @@ public Debugger(LaunchOptions launchOptions, Logger logger) _debuggeePids = new Dictionary(); Logger = logger; _miResults = new MIResults(logger); + MICommandFactory = MICommandFactory.GetInstance(launchOptions.DebuggerMIMode, this); } protected void SetDebuggerPid(int debuggerPid) @@ -197,7 +197,7 @@ private bool IsUnixDebuggerRunning() return false; } - private void RetryBreak(object o) + private void RetryBreak(object? o) { lock (_internalBreakActions) { @@ -230,7 +230,7 @@ public Task AddInternalBreakAction(Func func) { if (_internalBreakActionCompletionSource == null) { - _internalBreakActionCompletionSource = new TaskCompletionSource(); + _internalBreakActionCompletionSource = new TaskCompletionSource(); } _internalBreakActions.Enqueue(func); @@ -368,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) @@ -392,7 +392,7 @@ private async Task DoInternalBreakActions(bool fIsAsyncBreak) } catch (Exception e) when (ExceptionHelper.BeforeCatch(e, Logger, reportOnlyCorrupting: true)) { - if (firstException != null) + if (firstException is null) { firstException = e; } @@ -404,7 +404,7 @@ private async Task DoInternalBreakActions(bool fIsAsyncBreak) { if (this.IsClosed) { - source.TrySetException(new DebuggerDisposedException(_closeMessage)); + source.TrySetException(new DebuggerDisposedException(GetTargetProcessExitedReason())); } else { @@ -430,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; @@ -524,7 +524,7 @@ private void Close(string closeMessage) Debug.Assert(_closeMessage == null, "Why was Close called more than once? Should be impossible."); _closeMessage = closeMessage; - _transport.Close(); + _transport?.Close(); lock (_waitingOperations) { foreach (var value in _waitingOperations.Values) @@ -586,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() @@ -905,7 +905,7 @@ private Task CmdAsyncInternal(string command, ResultClass expectedResul { if (this.IsClosed) { - throw new DebuggerDisposedException(_closeMessage); + throw new DebuggerDisposedException(GetTargetProcessExitedReason()); } id = ++_lastCommandId; @@ -940,7 +940,7 @@ void ITransportCallback.OnStdOutLine(string line) if (_initializationLog != null) { _initializationLog.AddLast(line); - if (string.IsNullOrEmpty(_gdbVersion)) + if (IsNullOrEmpty(_gdbVersion)) { TryInitializeGdbVersion(line); } @@ -972,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) @@ -992,13 +992,13 @@ public void OnDebuggerProcessExit(/*OPTIONAL*/ string exitCode) if (isMinGWOrCygwin && IsUnsupportedWindowsGdbVersion(_gdbVersion)) { exception = new MIDebuggerInitializeFailedUnsupportedGdbException( - this.MICommandFactory.Name, _initialErrors.ToList().AsReadOnly(), _initializationLog.ToList().AsReadOnly(), _gdbVersion); + this.MICommandFactory.Name, (_initialErrors?.ToList() ?? new List()).AsReadOnly(), (_initializationLog?.ToList() ?? new List()).AsReadOnly(), _gdbVersion); SendUnsupportedWindowsGdbEvent(_gdbVersion); } else { exception = new MIDebuggerInitializeFailedException( - this.MICommandFactory.Name, _initialErrors.ToList().AsReadOnly(), _initializationLog.ToList().AsReadOnly()); + this.MICommandFactory.Name, (_initialErrors?.ToList() ?? new List()).AsReadOnly(), (_initializationLog?.ToList() ?? new List()).AsReadOnly()); } _initialErrors = null; @@ -1011,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); @@ -1029,7 +1029,7 @@ public void OnDebuggerProcessExit(/*OPTIONAL*/ string exitCode) { if (DebuggerExitEvent != null) { - DebuggerExitEvent(this, null); + DebuggerExitEvent(this, EventArgs.Empty); } } } @@ -1049,7 +1049,7 @@ void TryInitializeGdbVersion(string line) } int majorVersion = 0; - if (!string.IsNullOrWhiteSpace(_gdbVersion)) + if (!IsNullOrWhiteSpace(_gdbVersion)) { int.TryParse(_gdbVersion.Split('.').FirstOrDefault(), out majorVersion); } @@ -1110,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)) { @@ -1156,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 @@ -1240,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(); @@ -1250,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)) @@ -1272,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) { @@ -1316,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); @@ -1419,8 +1419,7 @@ this.LaunchOptions is LocalLaunchOptions && private void OnNotificationOutput(string cmd) { - Results results = null; - if ((results = MICommandFactory.IsModuleLoad(cmd)) != null) + if (MICommandFactory.IsModuleLoad(cmd) is Results results) { if (LibraryLoadEvent != null) { @@ -1460,12 +1459,12 @@ private void OnNotificationOutput(string cmd) else if (cmd.StartsWith("thread-created,", StringComparison.Ordinal)) { results = _miResults.ParseResultList(cmd.Substring("thread-created,".Length)); - ThreadCreatedEvent(this, new ResultEventArgs(results, 0)); + ThreadCreatedEvent?.Invoke(this, new ResultEventArgs(results, 0)); } else if (cmd.StartsWith("thread-exited,", StringComparison.Ordinal)) { results = _miResults.ParseResultList(cmd.Substring("thread-exited,".Length)); - ThreadExitedEvent(this, new ResultEventArgs(results, 0)); + ThreadExitedEvent?.Invoke(this, new ResultEventArgs(results, 0)); } else if (cmd.StartsWith("telemetry,", StringComparison.Ordinal)) { @@ -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,21 +1618,22 @@ private async void PostCommand(string cmd) private void SendToTransport(string cmd) { - _transport.Send(cmd); + ITransport transport = _transport ?? throw new InvalidOperationException(); + transport.Send(cmd); // https://github.com/Microsoft/MIEngine/issues/616 : // If it is local gdb (MinGW/Cygwin) on Windows, we need to send an extra line after commands // so that if it errors, the error will come through. if (this.SendNewLineAfterCmd) { - _transport.Send(String.Empty); + transport.Send(String.Empty); } } public static ulong ParseAddr(string addr, bool throwOnError = false) { ulong res = 0; - if (string.IsNullOrEmpty(addr)) + if (IsNullOrEmpty(addr)) { if (throwOnError) { @@ -1669,7 +1669,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) { @@ -1712,9 +1712,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..a139dd741 100644 --- a/src/MICore/JsonLaunchOptions.cs +++ b/src/MICore/JsonLaunchOptions.cs @@ -3,8 +3,8 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; +using System.Runtime.CompilerServices; using System.Runtime.Serialization; using Newtonsoft.Json; using Newtonsoft.Json.Converters; @@ -18,32 +18,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 +55,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 +85,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 +127,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 +157,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 +182,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 +223,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 +236,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 +261,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 +271,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 +346,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 +371,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 +383,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 +419,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 +455,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 +527,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 +548,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 +556,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 +572,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 +595,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 +614,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. @@ -661,7 +662,7 @@ public PipeTransportOptions() 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 +683,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 +705,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 +723,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 +739,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 +752,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 +785,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..04bab9797 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; @@ -74,18 +73,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 +93,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 +164,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,7 +196,7 @@ internal static string ReplaceDebuggerCommandToken(string cmdArgs, string comman } } - private static IList GetEnvironmentEntries(IDictionary env) + private static IList GetEnvironmentEntries(IDictionary? env) { List entries = new List(); @@ -225,7 +228,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 +238,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 +250,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 +285,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,14 +300,14 @@ public EnvironmentEntry(Xml.LaunchOptions.EnvironmentEntry xmlEntry) public EnvironmentEntry(Json.LaunchOptions.Environment jsonEntry) { - this.Name = jsonEntry.Name; - this.Value = jsonEntry.Value; + this.Name = LaunchOptions.RequireAttribute(jsonEntry.Name, nameof(jsonEntry.Name)); + this.Value = jsonEntry.Value ?? string.Empty; } - public EnvironmentEntry(string name, string value) + public EnvironmentEntry(string name, string? value) { this.Name = name; - this.Value = value; + this.Value = value ?? string.Empty; } /// @@ -331,7 +334,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 +343,7 @@ public string EditorPath } set { - if (string.IsNullOrEmpty(value)) + if (IsNullOrEmpty(value)) { throw new ArgumentNullException("EditorPath"); } @@ -349,7 +352,7 @@ public string EditorPath } - private string _compileTimePath; + private string _compileTimePath = string.Empty; public string CompileTimePath { get @@ -363,9 +366,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 +378,14 @@ 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); + var sourceMaps = new List(source?.Keys.Count ?? 0); - foreach (var item in source) + foreach (var item in source ?? new Dictionary()) { string compileTimePath = item.Key; - string editorPath = null; + string? editorPath = null; bool useForBreakpoints = true; if (item.Value is string value) @@ -394,7 +397,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 +412,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 +708,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 +788,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 +821,7 @@ public UnixShellPortLaunchOptions(string startRemoteDebuggerCommand, } string prefix = GetDebuginfodEnvironmentPrefix(); - if (!string.IsNullOrEmpty(prefix)) + if (!IsNullOrEmpty(prefix)) { this.StartRemoteDebuggerCommand = prefix + this.StartRemoteDebuggerCommand; } @@ -827,14 +834,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,13 +855,18 @@ 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 { - get { return _baseOptions; } + get + { + if (_baseOptions is null) + throw new InvalidOperationException("BaseOptions has not been initialized"); + return _baseOptions; + } protected set { if (value == null) @@ -865,18 +877,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) /// 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,11 +896,11 @@ public virtual string ExePath } } - private string _exeArguments; + private string? _exeArguments; /// /// [Optional] Additional arguments to specify when launching the process /// - public string ExeArguments + public string? ExeArguments { get { return _exeArguments; } set @@ -913,11 +925,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 { @@ -933,14 +945,14 @@ protected set } 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 +962,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 +976,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 +1083,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 +1105,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 +1128,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 +1169,9 @@ protected set } } - private ReadOnlyCollection _sourceMap; + private ReadOnlyCollection? _sourceMap; - public ReadOnlyCollection SourceMap + public ReadOnlyCollection? SourceMap { get { return _sourceMap; } set @@ -1335,36 +1355,37 @@ 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 && !IsNullOrWhiteSpace(customLauncherToken.Value())) { - string customLauncherName = parsedOptions["customLauncher"].Value(); + string customLauncherName = customLauncherToken.Value() ?? string.Empty; var jsonLauncher = configStore?.GetCustomLauncher(customLauncherName); if (jsonLauncher == null) { @@ -1376,7 +1397,7 @@ public static LaunchOptions GetInstance(HostConfigurationStore configStore, stri } launchOptions = ExecuteLauncher(configStore, (IPlatformAppLauncher)jsonLauncher, 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 +1508,38 @@ public static LaunchOptions GetInstance(HostConfigurationStore configStore, stri if (clsidLauncher != Guid.Empty) { + if (launcherXmlOptions 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) + { + 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)) + if (IsNullOrEmpty(launchOptions.ExeArguments)) launchOptions.ExeArguments = args; - if (string.IsNullOrEmpty(launchOptions.WorkingDirectory)) + if (IsNullOrEmpty(launchOptions.WorkingDirectory)) launchOptions.WorkingDirectory = dir; launchOptions.NoDebug = noDebug; @@ -1525,21 +1561,23 @@ public static LaunchOptions CreateForAttachRequest(Microsoft.VisualStudio.Debugg { var suppOptions = GetOptionsFromFile(logger); string connection; - ((IDebugPort2)unixPort).GetPortName(out connection); - AttachOptionsForConnection attachOptions = null; + ((IDebugPort2)unixPort).GetPortName(out connection!); + 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) { - string addr = ((Microsoft.VisualStudio.Debugger.Interop.UnixPortSupplier.IDebugGdbServerAttach)unixPort).GdbServerAttachProcess(processId, attachOptions.ServerOptions.PreAttachCommand); - options = new LocalLaunchOptions(attachOptions.ServerOptions.MIDebuggerPath, addr, attachOptions.ServerOptions.MIDebuggerArgs); + Debug.Assert(attachOptions?.ServerOptions is not null, "Server options should have been validated by isServerMode."); + var serverOptions = attachOptions.ServerOptions; + string addr = ((Microsoft.VisualStudio.Debugger.Interop.UnixPortSupplier.IDebugGdbServerAttach)unixPort).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 +1599,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 +1612,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 +1642,7 @@ internal static SupplementalLaunchOptions GetOptionsFromFile(Logger logger) return null; } - internal void LoadSupplementalOptions(Logger logger) + internal void LoadSupplementalOptions(Logger? logger) { if (SourceMap == null) { @@ -1615,7 +1653,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 +1692,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 +1703,7 @@ private void Merge(AttachOptionsForConnection suppOptions) AdditionalSOLibSearchPath += ';' + suppOptions.AdditionalSOLibSearchPath; } } - if (string.IsNullOrWhiteSpace(WorkingDirectory)) + if (IsNullOrWhiteSpace(WorkingDirectory)) { WorkingDirectory = suppOptions.WorkingDirectory; } @@ -1713,8 +1751,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 +1794,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 +1850,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 +1868,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 +1881,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 +1898,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 +1909,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 +1922,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 +1938,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 +1960,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 +1973,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 +2003,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 +2026,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 +2050,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 +2063,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 +2087,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) { @@ -2075,19 +2113,19 @@ private static List TryAddWindowsDebuggeeConsoleRedirection(List } } - return arguments; + return arguments ?? new List(); } 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 +2137,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 +2157,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,9 +2175,14 @@ 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); + if (configStore is null) + { + throw new ArgumentNullException(nameof(configStore)); + } + + var deviceAppLauncher = HostLoader.VsCoCreateManagedObject(configStore, clsidLauncher) as IPlatformAppLauncher; if (deviceAppLauncher == null) { throw new ArgumentException(string.Format(CultureInfo.CurrentCulture, MICoreResources.Error_LauncherNotFound, clsidLauncher.ToString("B"))); @@ -2147,7 +2190,7 @@ private static LaunchOptions ExecuteLauncher(HostConfigurationStore configStore, 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; @@ -2155,8 +2198,8 @@ private static LaunchOptions ExecuteLauncher(HostConfigurationStore configStore, { try { - deviceAppLauncher.Initialize(configStore, eventCallback); - deviceAppLauncher.SetLaunchOptions(exePath, args, dir, launcherOptions, targetEngine); + deviceAppLauncher.Initialize(configStore ?? throw new ArgumentNullException(nameof(configStore)), eventCallback ?? throw new ArgumentNullException(nameof(eventCallback))); + deviceAppLauncher.SetLaunchOptions(exePath, args ?? string.Empty, dir ?? string.Empty, launcherOptions, targetEngine); } catch (Exception e) when (!(e is InvalidLaunchOptionsException) && ExceptionHelper.BeforeCatch(e, logger, reportOnlyCorrupting: true)) { @@ -2181,7 +2224,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 +2232,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 +2244,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 +2357,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 +2382,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 + '"'; } diff --git a/src/MICore/Logger.cs b/src/MICore/Logger.cs index 361d34722..d3d38b18d 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; }; @@ -85,7 +84,10 @@ public static void LoadMIDebugLogger() { // command configured log file HostLogger.Reset(); HostLogger.SetEngineLogFile(CmdLogInfo.logFile); - HostLogger.EnableHostLogging(CmdLogInfo.logToOutput); + if (CmdLogInfo.logToOutput != null) + { + HostLogger.EnableHostLogging(CmdLogInfo.logToOutput); + } } s_isEnabled = true; @@ -149,7 +151,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 +204,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..b025b8737 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; + ParseError(resultString, "value expected", resultStr); } - 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; + throw new InvalidOperationException(); } /// @@ -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; + ParseError(resultString, "result value expected", resultStr); } - 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; + throw new InvalidOperationException(); } /// @@ -853,23 +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); - return null; - } - string name = resultStr.Prefix(equals).Extract(_resultString); - ResultValue value = ParseResultValue(resultStr.Advance(equals + 1), out rest); - if (value == null) - { + ParseError(resultString, "variable not found", resultStr); return null; } + string name = resultStr.Prefix(equals).Extract(resultString); + ResultValue value = ParseResultValue(resultString, resultStr.Advance(equals + 1), out rest); return new NamedResultValue(name, value); } @@ -890,13 +865,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 +879,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 +898,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 +908,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 +921,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 +978,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,22 +994,12 @@ private List ParseResultList(EdgeCondition begin, EdgeConditio return list; } input = input.AdvanceTo(i); - var item = ParseResult(input, out rest); - if (item == null) - { - ParseError("Result expected", input); - return null; - } + var item = ParseResult(resultString, input, out rest); list.Add(item); input = rest; - while (!input.IsEmpty && _resultString[input.Start] == ',') + while (!input.IsEmpty && resultString[input.Start] == ',') { - item = ParseResult(input.Advance(1), out rest); - if (item == null) - { - ParseError("Result expected", input); - return null; - } + item = ParseResult(resultString, input.Advance(1), out rest); list.Add(item); input = rest; } @@ -1042,7 +1007,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 +1015,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 +1027,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 +1040,17 @@ 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); - if (list == null) - { - return null; - } + var list = ParseResultList(resultString, '{', '}', input, out rest); 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); } v = new TupleValue(list); if (tlist.Count != 0) @@ -1103,78 +1064,64 @@ 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); - if (list == null) - { - return null; - } + var list = ParseResultList(resultString, '{', '}', input, out rest); return new TupleValue(list); } /// /// 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); - if (item == null) - { - ParseError("Value expected", input); - return null; - } + var item = ParseValue(resultString, input, out rest); list.Add(item); input = rest; - while (!input.IsEmpty && _resultString[input.Start] == ',') + while (!input.IsEmpty && resultString[input.Start] == ',') { - item = ParseValue(input.Advance(1), out rest); - if (item == null) - { - ParseError("Value expected", input); - return null; - } + item = ParseValue(resultString, input.Advance(1), out rest); 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,36 +1132,34 @@ 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); - if (list == null) - { - return null; - } + var list = ParseResultList(resultString, '[', ']', input, out rest); return new ResultListValue(list); } - private void ParseError(string message, Span input) + [DoesNotReturn] + 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)); + throw new InvalidOperationException(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..af92743cb 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 { @@ -79,7 +79,7 @@ public static void SetEnvironmentVariable(this ProcessStartInfo processStartInfo } // 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..46f45c9d6 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; diff --git a/src/MICore/Transports/ClientServerTransport.cs b/src/MICore/Transports/ClientServerTransport.cs index 88cee7c06..a8f0b1e8b 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); @@ -66,7 +65,7 @@ public void Send(string cmd) _clientTransport.Send(cmd); } - public int ExecuteSyncCommand(string commandDescription, string commandText, int timeout, out string output, out string error) + public int ExecuteSyncCommand(string commandDescription, string commandText, int timeout, out string? output, out string? error) { return _clientTransport.ExecuteSyncCommand(commandDescription, commandText, timeout, out output, out error); } diff --git a/src/MICore/Transports/ITransport.cs b/src/MICore/Transports/ITransport.cs index ca377a865..3a41f9ac6 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; } @@ -36,7 +36,7 @@ public interface ITransport /// Output of the command in stdout /// Output of the command in stderr /// Exit code of the command - int ExecuteSyncCommand(string commandDescription, string commandText, int timeout, out string output, out string error); + int ExecuteSyncCommand(string commandDescription, string commandText, int timeout, out string? output, out string? error); bool CanExecuteCommand(); } @@ -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..c58e8e6e6 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] == '-') { @@ -104,7 +109,7 @@ private void TransportLoop() } } - public int ExecuteSyncCommand(string commandDescription, string commandText, int timeout, out string output, out string error) + public int ExecuteSyncCommand(string commandDescription, string commandText, int timeout, out string? output, out string? error) { throw new NotImplementedException(); } diff --git a/src/MICore/Transports/PipeTransport.cs b/src/MICore/Transports/PipeTransport.cs index c456ac590..86ae17d4c 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 = null; + string? error = null; string pipeArgs = PipeLaunchOptions.ReplaceDebuggerCommandToken(_cmdArgs, commandText, true); string fullCommand = string.Format(CultureInfo.InvariantCulture, "{0} {1}", _pipePath, pipeArgs); @@ -373,8 +377,10 @@ private int WrappedExecuteSyncCommand(string commandDescription, string commandT return exitCode; } - public override int ExecuteSyncCommand(string commandDescription, string commandText, int timeout, out string output, out string error) + public override int ExecuteSyncCommand(string commandDescription, string commandText, int timeout, out string? output, out string? error) { + Debug.Assert(_cmdArgs is not null && _pipePath is not null, "Should be impossible -- cannot send commands until Init"); + output = null; error = null; int exitCode = -1; @@ -382,7 +388,7 @@ public override int ExecuteSyncCommand(string commandDescription, string command 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; diff --git a/src/MICore/Transports/RunInTerminalTransport.cs b/src/MICore/Transports/RunInTerminalTransport.cs index a9fbb8fc5..f599d4ad4 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) { @@ -345,7 +349,7 @@ public override bool CanExecuteCommand() return false; } - public override int ExecuteSyncCommand(string commandDescription, string commandText, int timeout, out string output, out string error) + public override int ExecuteSyncCommand(string commandDescription, string commandText, int timeout, out string? output, out string? error) { throw new NotImplementedException(); } 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..feec10593 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; + } } /// @@ -240,7 +247,7 @@ protected static void ForceDisposeStreamReader(StreamReader reader) reader?.Dispose(); } - public abstract int ExecuteSyncCommand(string commandDescription, string commandText, int timeout, out string output, out string error); + public abstract int ExecuteSyncCommand(string commandDescription, string commandText, int timeout, out string? output, out string? error); public abstract bool CanExecuteCommand(); } } diff --git a/src/MICore/Transports/TcpTransport.cs b/src/MICore/Transports/TcpTransport.cs index 72bb93145..4635b1214 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,10 +83,10 @@ 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) + public override int ExecuteSyncCommand(string commandDescription, string commandText, int timeout, out string? output, out string? error) { throw new NotImplementedException(); } diff --git a/src/MICore/Transports/UnixShellPortTransport.cs b/src/MICore/Transports/UnixShellPortTransport.cs index 95cd72933..ad30a62e0 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 ?? "???")); @@ -126,8 +131,9 @@ void IDebugUnixShellCommandCallback.OnExit(string exitCode) } } - public int ExecuteSyncCommand(string commandDescription, string commandText, int timeout, out string output, out string error) + 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. _launchOptions.UnixPort.ExecuteSyncCommand(commandDescription, commandText, out output, timeout, out 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; diff --git a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs index 804709bc2..1d6cad0dc 100755 --- a/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs +++ b/src/MIDebugEngine/Engine.Impl/DebuggedProcess.cs @@ -54,7 +54,8 @@ internal class DebuggedProcess : MICore.Debugger private string _entryPointBreakpoint = string.Empty; private bool? _simpleValuesExcludesRefTypes = null; - public DebuggedProcess(bool bLaunched, LaunchOptions launchOptions, ISampleEngineCallback callback, WorkerThread worker, BreakpointManager bpman, AD7Engine engine, HostConfigurationStore configStore, HostWaitLoop waitLoop = null) : base(launchOptions, engine.Logger) + public DebuggedProcess(bool bLaunched, LaunchOptions launchOptions, ISampleEngineCallback callback, WorkerThread worker, BreakpointManager bpman, AD7Engine engine, HostConfigurationStore configStore, HostWaitLoop waitLoop = null) : + base(launchOptions, engine.Logger) { uint processExitCode = 0; _pendingMessages = new StringBuilder(400); @@ -64,7 +65,6 @@ public DebuggedProcess(bool bLaunched, LaunchOptions launchOptions, ISampleEngin _libraryLoaded = new List(); _loadOrder = 0; _deleteEntryPointBreakpoint = false; - MICommandFactory = MICommandFactory.GetInstance(launchOptions.DebuggerMIMode, this); _waitDialog = (MICommandFactory.SupportsStopOnDynamicLibLoad() && launchOptions.WaitDynamicLibLoad) ? new HostWaitDialog(ResourceStrings.LoadingSymbolMessage, ResourceStrings.LoadingSymbolCaption) : null; Natvis = new Natvis.Natvis(this, launchOptions.ShowDisplayString, configStore);