From e59c9260eb78bbf093d8081a3b8a1ecc0132d70f Mon Sep 17 00:00:00 2001 From: Stephen Toub Date: Tue, 3 Dec 2019 10:28:54 -0500 Subject: [PATCH] Annotate System.IO.FileSystem.Watcher for nullable reference types (#456) --- .../src/System/IO/PathInternal.Windows.cs | 3 +- .../src/System/Text/ValueUtf8Converter.cs | 5 +- .../ref/System.IO.FileSystem.Watcher.cs | 30 ++++----- .../ref/System.IO.FileSystem.Watcher.csproj | 1 + .../src/System.IO.FileSystem.Watcher.csproj | 1 + .../src/System/IO/FileSystemEventArgs.cs | 8 +-- .../src/System/IO/FileSystemWatcher.Linux.cs | 66 +++++++------------ .../src/System/IO/FileSystemWatcher.OSX.cs | 40 +++++------ .../src/System/IO/FileSystemWatcher.Win32.cs | 13 ++-- .../src/System/IO/FileSystemWatcher.cs | 50 +++++++------- .../IO/InternalBufferOverflowException.cs | 4 +- .../src/System/IO/RenamedEventArgs.cs | 6 +- .../src/System/IO/WaitForChangedResult.cs | 6 +- .../src/System.IO.Ports.csproj | 1 + 14 files changed, 111 insertions(+), 123 deletions(-) diff --git a/src/libraries/Common/src/System/IO/PathInternal.Windows.cs b/src/libraries/Common/src/System/IO/PathInternal.Windows.cs index 1287b5605b484..c7fcd25bb078c 100644 --- a/src/libraries/Common/src/System/IO/PathInternal.Windows.cs +++ b/src/libraries/Common/src/System/IO/PathInternal.Windows.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. #nullable enable +using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; namespace System.IO @@ -74,7 +75,7 @@ private static bool EndsWithPeriodOrSpace(string path) /// away from paths during normalization, but if we see such a path at this point it should be /// normalized and has retained the final characters. (Typically from one of the *Info classes) /// - /// TODO: add atribute [return: NotNullIfNotNull("path")] + [return: NotNullIfNotNull("path")] internal static string? EnsureExtendedPrefixIfNeeded(string? path) { if (path != null && (path.Length >= MaxShortPath || EndsWithPeriodOrSpace(path))) diff --git a/src/libraries/Common/src/System/Text/ValueUtf8Converter.cs b/src/libraries/Common/src/System/Text/ValueUtf8Converter.cs index 3a2ba294db397..7d8aaae592b46 100644 --- a/src/libraries/Common/src/System/Text/ValueUtf8Converter.cs +++ b/src/libraries/Common/src/System/Text/ValueUtf8Converter.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +#nullable enable using System.Buffers; namespace System.Text @@ -13,7 +14,7 @@ namespace System.Text /// internal ref struct ValueUtf8Converter { - private byte[] _arrayToReturnToPool; + private byte[]? _arrayToReturnToPool; private Span _bytes; public ValueUtf8Converter(Span initialBuffer) @@ -40,7 +41,7 @@ public Span ConvertAndTerminateString(ReadOnlySpan value) public void Dispose() { - byte[] toReturn = _arrayToReturnToPool; + byte[]? toReturn = _arrayToReturnToPool; if (toReturn != null) { _arrayToReturnToPool = null; diff --git a/src/libraries/System.IO.FileSystem.Watcher/ref/System.IO.FileSystem.Watcher.cs b/src/libraries/System.IO.FileSystem.Watcher/ref/System.IO.FileSystem.Watcher.cs index 95ecae6ada3d3..86a892b048292 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/ref/System.IO.FileSystem.Watcher.cs +++ b/src/libraries/System.IO.FileSystem.Watcher/ref/System.IO.FileSystem.Watcher.cs @@ -15,10 +15,10 @@ public ErrorEventArgs(System.Exception exception) { } public delegate void ErrorEventHandler(object sender, System.IO.ErrorEventArgs e); public partial class FileSystemEventArgs : System.EventArgs { - public FileSystemEventArgs(System.IO.WatcherChangeTypes changeType, string directory, string name) { } + public FileSystemEventArgs(System.IO.WatcherChangeTypes changeType, string directory, string? name) { } public System.IO.WatcherChangeTypes ChangeType { get { throw null; } } public string FullPath { get { throw null; } } - public string Name { get { throw null; } } + public string? Name { get { throw null; } } } public delegate void FileSystemEventHandler(object sender, System.IO.FileSystemEventArgs e); public partial class FileSystemWatcher : System.ComponentModel.Component, System.ComponentModel.ISupportInitialize @@ -33,13 +33,13 @@ public FileSystemWatcher(string path, string filter) { } public int InternalBufferSize { get { throw null; } set { } } public System.IO.NotifyFilters NotifyFilter { get { throw null; } set { } } public string Path { get { throw null; } set { } } - public override System.ComponentModel.ISite Site { get { throw null; } set { } } - public System.ComponentModel.ISynchronizeInvoke SynchronizingObject { get { throw null; } set { } } - public event System.IO.FileSystemEventHandler Changed { add { } remove { } } - public event System.IO.FileSystemEventHandler Created { add { } remove { } } - public event System.IO.FileSystemEventHandler Deleted { add { } remove { } } - public event System.IO.ErrorEventHandler Error { add { } remove { } } - public event System.IO.RenamedEventHandler Renamed { add { } remove { } } + public override System.ComponentModel.ISite? Site { get { throw null; } set { } } + public System.ComponentModel.ISynchronizeInvoke? SynchronizingObject { get { throw null; } set { } } + public event System.IO.FileSystemEventHandler? Changed { add { } remove { } } + public event System.IO.FileSystemEventHandler? Created { add { } remove { } } + public event System.IO.FileSystemEventHandler? Deleted { add { } remove { } } + public event System.IO.ErrorEventHandler? Error { add { } remove { } } + public event System.IO.RenamedEventHandler? Renamed { add { } remove { } } public void BeginInit() { } protected override void Dispose(bool disposing) { } public void EndInit() { } @@ -55,8 +55,8 @@ public partial class InternalBufferOverflowException : System.SystemException { public InternalBufferOverflowException() { } protected InternalBufferOverflowException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) { } - public InternalBufferOverflowException(string message) { } - public InternalBufferOverflowException(string message, System.Exception inner) { } + public InternalBufferOverflowException(string? message) { } + public InternalBufferOverflowException(string? message, System.Exception? inner) { } } [System.FlagsAttribute] public enum NotifyFilters @@ -72,9 +72,9 @@ public enum NotifyFilters } public partial class RenamedEventArgs : System.IO.FileSystemEventArgs { - public RenamedEventArgs(System.IO.WatcherChangeTypes changeType, string directory, string name, string oldName) : base (default(System.IO.WatcherChangeTypes), default(string), default(string)) { } + public RenamedEventArgs(System.IO.WatcherChangeTypes changeType, string directory, string? name, string? oldName) : base (default(System.IO.WatcherChangeTypes), default(string), default(string)) { } public string OldFullPath { get { throw null; } } - public string OldName { get { throw null; } } + public string? OldName { get { throw null; } } } public delegate void RenamedEventHandler(object sender, System.IO.RenamedEventArgs e); public partial struct WaitForChangedResult @@ -82,8 +82,8 @@ public partial struct WaitForChangedResult private object _dummy; private int _dummyPrimitive; public System.IO.WatcherChangeTypes ChangeType { readonly get { throw null; } set { } } - public string Name { readonly get { throw null; } set { } } - public string OldName { readonly get { throw null; } set { } } + public string? Name { readonly get { throw null; } set { } } + public string? OldName { readonly get { throw null; } set { } } public bool TimedOut { readonly get { throw null; } set { } } } [System.FlagsAttribute] diff --git a/src/libraries/System.IO.FileSystem.Watcher/ref/System.IO.FileSystem.Watcher.csproj b/src/libraries/System.IO.FileSystem.Watcher/ref/System.IO.FileSystem.Watcher.csproj index 56d3123638433..c0ad246b21ab3 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/ref/System.IO.FileSystem.Watcher.csproj +++ b/src/libraries/System.IO.FileSystem.Watcher/ref/System.IO.FileSystem.Watcher.csproj @@ -1,5 +1,6 @@ + enable netcoreapp-Debug;netcoreapp-Release diff --git a/src/libraries/System.IO.FileSystem.Watcher/src/System.IO.FileSystem.Watcher.csproj b/src/libraries/System.IO.FileSystem.Watcher/src/System.IO.FileSystem.Watcher.csproj index 6fb40ad751b31..154aea675ba58 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/src/System.IO.FileSystem.Watcher.csproj +++ b/src/libraries/System.IO.FileSystem.Watcher/src/System.IO.FileSystem.Watcher.csproj @@ -1,6 +1,7 @@ true + enable netcoreapp-FreeBSD-Debug;netcoreapp-FreeBSD-Release;netcoreapp-Linux-Debug;netcoreapp-Linux-Release;netcoreapp-OSX-Debug;netcoreapp-OSX-Release;netcoreapp-Windows_NT-Debug;netcoreapp-Windows_NT-Release diff --git a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemEventArgs.cs b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemEventArgs.cs index 8df624b6a9961..84c963d3993be 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemEventArgs.cs +++ b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemEventArgs.cs @@ -10,13 +10,13 @@ namespace System.IO public class FileSystemEventArgs : EventArgs { private readonly WatcherChangeTypes _changeType; - private readonly string _name; + private readonly string? _name; private readonly string _fullPath; /// /// Initializes a new instance of the class. /// - public FileSystemEventArgs(WatcherChangeTypes changeType, string directory, string name) + public FileSystemEventArgs(WatcherChangeTypes changeType, string directory, string? name) { _changeType = changeType; _name = name; @@ -31,7 +31,7 @@ public FileSystemEventArgs(WatcherChangeTypes changeType, string directory, stri /// This is like Path.Combine, except without argument validation, /// and a separator is used even if the name argument is empty. /// - internal static string Combine(string directoryPath, string name) + internal static string Combine(string directoryPath, string? name) { bool hasSeparator = false; if (directoryPath.Length > 0) @@ -71,7 +71,7 @@ public string FullPath /// /// Gets the name of the affected file or directory. /// - public string Name + public string? Name { get { diff --git a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs index 25789da6b2533..def4ae5681fe6 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs +++ b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs @@ -40,7 +40,7 @@ private void StartRaisingEvents() switch (error.Error) { case Interop.Error.EMFILE: - string maxValue = ReadMaxUserLimit(MaxUserInstancesPath); + string? maxValue = ReadMaxUserLimit(MaxUserInstancesPath); string message = !string.IsNullOrEmpty(maxValue) ? SR.Format(SR.IOException_INotifyInstanceUserLimitExceeded_Value, maxValue) : SR.IOException_INotifyInstanceUserLimitExceeded; @@ -125,12 +125,12 @@ private void FinalizeDispose() /// Cancellation for the currently running watch operation. /// This is non-null if an operation has been started and null if stopped. /// - private CancellationTokenSource _cancellation; + private CancellationTokenSource? _cancellation; /// Reads the value of a max user limit path from procfs. /// The path to read. /// The value read, or "0" if a failure occurred. - private static string ReadMaxUserLimit(string path) + private static string? ReadMaxUserLimit(string path) { try { return File.ReadAllText(path).Trim(); } catch { return null; } @@ -301,7 +301,7 @@ internal RunningInstance( internal void Start() { // Schedule a task to read from the inotify queue and process the events. - Task.Factory.StartNew(obj => ((RunningInstance)obj).ProcessEvents(), + Task.Factory.StartNew(obj => ((RunningInstance)obj!).ProcessEvents(), this, CancellationToken.None, TaskCreationOptions.LongRunning, TaskScheduler.Default); // PERF: As needed, we can look into making this use async I/O rather than burning @@ -337,7 +337,7 @@ private void AddDirectoryWatch(WatchedDirectory parent, string directoryName) /// Adds a watch on a directory to the existing inotify handle. /// The parent directory entry. /// The new directory path to monitor, relative to the root. - private void AddDirectoryWatchUnlocked(WatchedDirectory parent, string directoryName) + private void AddDirectoryWatchUnlocked(WatchedDirectory? parent, string directoryName) { string fullPath = parent != null ? parent.GetPath(false, directoryName) : directoryName; @@ -363,7 +363,7 @@ private void AddDirectoryWatchUnlocked(WatchedDirectory parent, string directory Exception exc; if (error.Error == Interop.Error.ENOSPC) { - string maxValue = ReadMaxUserLimit(MaxUserWatchesPath); + string? maxValue = ReadMaxUserLimit(MaxUserWatchesPath); string message = !string.IsNullOrEmpty(maxValue) ? SR.Format(SR.IOException_INotifyWatchesUserLimitExceeded_Value, maxValue) : SR.IOException_INotifyWatchesUserLimitExceeded; @@ -374,8 +374,7 @@ private void AddDirectoryWatchUnlocked(WatchedDirectory parent, string directory exc = Interop.GetExceptionForIoErrno(error, fullPath); } - FileSystemWatcher watcher; - if (_weakWatcher.TryGetTarget(out watcher)) + if (_weakWatcher.TryGetTarget(out FileSystemWatcher? watcher)) { watcher.OnError(new ErrorEventArgs(exc)); } @@ -384,7 +383,7 @@ private void AddDirectoryWatchUnlocked(WatchedDirectory parent, string directory } // Then store the path information into our map. - WatchedDirectory directoryEntry; + WatchedDirectory? directoryEntry; bool isNewDirectory = false; if (_wdToPathMap.TryGetValue(wd, out directoryEntry)) { @@ -396,10 +395,7 @@ private void AddDirectoryWatchUnlocked(WatchedDirectory parent, string directory // of the world, but there's little that can be done about that.) if (directoryEntry.Parent != parent) { - if (directoryEntry.Parent != null) - { - directoryEntry.Parent.Children.Remove (directoryEntry); - } + directoryEntry.Parent?.Children!.Remove (directoryEntry); directoryEntry.Parent = parent; if (parent != null) { @@ -452,10 +448,7 @@ private void RemoveWatchedDirectory(WatchedDirectory directoryEntry, bool remove Debug.Assert (_includeSubdirectories); lock (SyncObj) { - if (directoryEntry.Parent != null) - { - directoryEntry.Parent.Children.Remove (directoryEntry); - } + directoryEntry.Parent?.Children!.Remove(directoryEntry); RemoveWatchedDirectoryUnlocked (directoryEntry, removeInotify); } } @@ -519,12 +512,12 @@ private void ProcessEvents() // When cancellation is requested, clear out all watches. This should force any active or future reads // on the inotify handle to return 0 bytes read immediately, allowing us to wake up from the blocking call // and exit the processing loop and clean up. - var ctr = _cancellationToken.UnsafeRegister(obj => ((RunningInstance)obj).CancellationCallback(), this); + var ctr = _cancellationToken.UnsafeRegister(obj => ((RunningInstance)obj!).CancellationCallback(), this); try { // Previous event information ReadOnlySpan previousEventName = ReadOnlySpan.Empty; - WatchedDirectory previousEventParent = null; + WatchedDirectory? previousEventParent = null; uint previousEventCookie = 0; // Process events as long as we're not canceled and there are more to read... @@ -535,7 +528,7 @@ private void ProcessEvents() // so as to avoid a rooted cycle that would prevent our processing loop from ever ending // if the watcher is dropped by the user without being disposed. If we can't get the watcher, // there's nothing more to do (we can't raise events), so bail. - FileSystemWatcher watcher; + FileSystemWatcher? watcher; if (!_weakWatcher.TryGetTarget(out watcher)) { break; @@ -543,7 +536,7 @@ private void ProcessEvents() uint mask = nextEvent.mask; ReadOnlySpan expandedName = ReadOnlySpan.Empty; - WatchedDirectory associatedDirectoryEntry = null; + WatchedDirectory? associatedDirectoryEntry = null; // An overflow event means that we can't trust our state without restarting since we missed events and // some of those events could be a directory create, meaning we wouldn't have added the directory to the @@ -729,8 +722,7 @@ private void ProcessEvents() } catch (Exception exc) { - FileSystemWatcher watcher; - if (_weakWatcher.TryGetTarget(out watcher)) + if (_weakWatcher.TryGetTarget(out FileSystemWatcher? watcher)) { watcher.OnError(new ErrorEventArgs(exc)); } @@ -852,32 +844,22 @@ private sealed class WatchedDirectory { /// A StringBuilder cached on the current thread to avoid allocations when possible. [ThreadStatic] - private static StringBuilder t_builder; + private static StringBuilder? t_builder; /// The parent directory. - internal WatchedDirectory Parent; + internal WatchedDirectory? Parent; /// The watch descriptor associated with this directory. internal int WatchDescriptor; /// The filename of this directory. - internal string Name; + internal string? Name; /// Child directories of this directory for which we added explicit watches. - internal List Children; + internal List? Children; /// Child directories of this directory for which we added explicit watches. This is the same as Children, but ensured to be initialized as non-null. - internal List InitializedChildren - { - get - { - if (Children == null) - { - Children = new List (); - } - return Children; - } - } + internal List InitializedChildren => Children ??= new List(); // PERF: Work is being done here proportionate to depth of watch directories. // If this becomes a bottleneck, we'll need to come up with another mechanism @@ -894,14 +876,10 @@ internal List InitializedChildren /// Whether to get a path relative to the root directory being watched, or a full path. /// An additional name to include in the path, relative to this directory. /// The computed path. - internal string GetPath(bool relativeToRoot, string additionalName = null) + internal string GetPath(bool relativeToRoot, string? additionalName = null) { // Use our cached builder - StringBuilder builder = t_builder; - if (builder == null) - { - t_builder = builder = new StringBuilder(); - } + StringBuilder builder = (t_builder ??= new StringBuilder()); builder.Clear(); // Write the directory's path. Then if an additional filename was supplied, append it diff --git a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.OSX.cs b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.OSX.cs index 77a920968503b..f5e2d88a94098 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.OSX.cs +++ b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.OSX.cs @@ -3,7 +3,6 @@ // See the LICENSE file in the project root for more information. using System.Buffers; -using System.Collections.Generic; using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; @@ -67,7 +66,7 @@ private void StopRaisingEvents() if (IsSuspended()) return; - CancellationTokenSource token = _cancellation; + CancellationTokenSource? token = _cancellation; if (token != null) { _cancellation = null; @@ -80,7 +79,7 @@ private void StopRaisingEvents() // ---- PAL layer ends here ---- // ----------------------------- - private CancellationTokenSource _cancellation; + private CancellationTokenSource? _cancellation; private static FSEventStreamEventFlags TranslateFlags(NotifyFilters flagsToTranslate) { @@ -141,11 +140,11 @@ private sealed class RunningInstance private FSEventStreamEventFlags _filterFlags; // The EventStream to listen for events on - private SafeEventStreamHandle _eventStream; + private SafeEventStreamHandle? _eventStream; // Callback delegate for the EventStream events - private Interop.EventStream.FSEventStreamCallback _callback; + private Interop.EventStream.FSEventStreamCallback? _callback; // Token to monitor for cancellation requests, upon which processing is stopped and all // state is cleaned up. @@ -154,7 +153,7 @@ private sealed class RunningInstance // Calling RunLoopStop multiple times SegFaults so protect the call to it private bool _stopping; - private ExecutionContext _context; + private ExecutionContext? _context; internal RunningInstance( FileSystemWatcher watcher, @@ -171,7 +170,7 @@ internal RunningInstance( _includeChildren = includeChildren; _filterFlags = filter; _cancellationToken = cancelToken; - _cancellationToken.UnsafeRegister(obj => ((RunningInstance)obj).CancellationCallback(), this); + _cancellationToken.UnsafeRegister(obj => ((RunningInstance)obj!).CancellationCallback(), this); _stopping = false; } @@ -199,7 +198,13 @@ public static void ScheduleEventStream(SafeEventStreamHandle eventStream) Debug.Assert(s_scheduledStreamsCount == 0); s_scheduledStreamsCount = 1; var runLoopStarted = new ManualResetEventSlim(); - new Thread(WatchForFileSystemEventsThreadStart) { IsBackground = true }.Start(new object[] { runLoopStarted, eventStream }); + new Thread(args => + { + object[] inputArgs = (object[])args!; + WatchForFileSystemEventsThreadStart((ManualResetEventSlim)inputArgs[0], (SafeEventStreamHandle)inputArgs[1]); + }) + { IsBackground = true }.Start(new object[] { runLoopStarted, eventStream }); + runLoopStarted.Wait(); } } @@ -225,11 +230,8 @@ public static void UnscheduleFromRunLoop(SafeEventStreamHandle eventStream) } } - private static void WatchForFileSystemEventsThreadStart(object args) + private static void WatchForFileSystemEventsThreadStart(ManualResetEventSlim runLoopStarted, SafeEventStreamHandle eventStream) { - var inputArgs = (object[])args; - var runLoopStarted = (ManualResetEventSlim)inputArgs[0]; - var _eventStream = (SafeEventStreamHandle)inputArgs[1]; // Get this thread's RunLoop IntPtr runLoop = Interop.RunLoop.CFRunLoopGetCurrent(); s_watcherRunLoop = runLoop; @@ -240,7 +242,7 @@ private static void WatchForFileSystemEventsThreadStart(object args) Debug.Assert(retainResult == runLoop, "CFRetain is supposed to return the input value"); // Schedule the EventStream to run on the thread's RunLoop - Interop.EventStream.FSEventStreamScheduleWithRunLoop(_eventStream, runLoop, Interop.RunLoop.kCFRunLoopDefaultMode); + Interop.EventStream.FSEventStreamScheduleWithRunLoop(eventStream, runLoop, Interop.RunLoop.kCFRunLoopDefaultMode); runLoopStarted.Set(); try @@ -260,7 +262,7 @@ private static void WatchForFileSystemEventsThreadStart(object args) private void CancellationCallback() { - SafeEventStreamHandle eventStream = _eventStream; + SafeEventStreamHandle? eventStream = _eventStream; if (!_stopping && eventStream != null) { _stopping = true; @@ -343,8 +345,7 @@ internal unsafe void Start() if (!started) { // Try to get the Watcher to raise the error event; if we can't do that, just silently exit since the watcher is gone anyway - FileSystemWatcher watcher; - if (_weakWatcher.TryGetTarget(out watcher)) + if (_weakWatcher.TryGetTarget(out FileSystemWatcher? watcher)) { // An error occurred while trying to start the run loop so fail out watcher.OnError(new ErrorEventArgs(new IOException(SR.EventStream_FailedToStart, Marshal.GetLastWin32Error()))); @@ -364,14 +365,13 @@ private unsafe void FileSystemEventCallback( // so as to avoid a rooted cycle that would prevent our processing loop from ever ending // if the watcher is dropped by the user without being disposed. If we can't get the watcher, // there's nothing more to do (we can't raise events), so bail. - FileSystemWatcher watcher; - if (!_weakWatcher.TryGetTarget(out watcher)) + if (!_weakWatcher.TryGetTarget(out FileSystemWatcher? watcher)) { CancellationCallback(); return; } - ExecutionContext context = _context; + ExecutionContext? context = _context; if (context is null) { // Flow suppressed, just run here @@ -381,7 +381,7 @@ private unsafe void FileSystemEventCallback( { ExecutionContext.Run( context, - (object o) => ((RunningInstance)o).ProcessEvents(numEvents.ToInt32(), eventPaths, new Span(eventFlags, numEvents.ToInt32()), new Span(eventIds, numEvents.ToInt32()), watcher), + (object? o) => ((RunningInstance)o!).ProcessEvents(numEvents.ToInt32(), eventPaths, new Span(eventFlags, numEvents.ToInt32()), new Span(eventIds, numEvents.ToInt32()), watcher), this); } } diff --git a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Win32.cs b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Win32.cs index ceeca5097e9ce..8df1800c7ccfd 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Win32.cs +++ b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Win32.cs @@ -4,6 +4,7 @@ using System.ComponentModel; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Threading; using Microsoft.Win32.SafeHandles; @@ -56,9 +57,9 @@ private void StartRaisingEvents() { state.PreAllocatedOverlapped = new PreAllocatedOverlapped((errorCode, numBytes, overlappedPointer) => { - AsyncReadState state = (AsyncReadState)ThreadPoolBoundHandle.GetNativeOverlappedState(overlappedPointer); + AsyncReadState state = (AsyncReadState)ThreadPoolBoundHandle.GetNativeOverlappedState(overlappedPointer)!; state.ThreadPoolBinding.FreeNativeOverlapped(overlappedPointer); - if (state.WeakWatcher.TryGetTarget(out FileSystemWatcher watcher)) + if (state.WeakWatcher.TryGetTarget(out FileSystemWatcher? watcher)) { watcher.ReadDirectoryChangesCallback(errorCode, numBytes, state); } @@ -123,9 +124,9 @@ private void FinalizeDispose() private int _currentSession; // Unmanaged handle to monitored directory - private SafeFileHandle _directoryHandle; + private SafeFileHandle? _directoryHandle; - private static bool IsHandleInvalid(SafeFileHandle handle) + private static bool IsHandleInvalid([NotNullWhen(false)] SafeFileHandle? handle) { return handle == null || handle.IsInvalid || handle.IsClosed; } @@ -137,6 +138,8 @@ private static bool IsHandleInvalid(SafeFileHandle handle) /// private unsafe void Monitor(AsyncReadState state) { + Debug.Assert(state.PreAllocatedOverlapped != null); + // This method should only ever access the directory handle via the state object passed in, and not access it // via _directoryHandle. While this function is executing asynchronously, another thread could set // EnableRaisingEvents to false and then back to true, restarting the FSW and causing a new directory handle @@ -362,7 +365,7 @@ internal AsyncReadState(int session, byte[] buffer, SafeFileHandle handle, Threa internal byte[] Buffer { get; } internal SafeFileHandle DirectoryHandle { get; } internal ThreadPoolBoundHandle ThreadPoolBinding { get; } - internal PreAllocatedOverlapped PreAllocatedOverlapped { get; set; } + internal PreAllocatedOverlapped? PreAllocatedOverlapped { get; set; } internal WeakReference WeakWatcher { get; } } } diff --git a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.cs b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.cs index f52beaf62520c..79e3c5e11a9c0 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.cs +++ b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.cs @@ -46,11 +46,11 @@ public partial class FileSystemWatcher : Component, ISupportInitialize private bool _disposed; // Event handlers - private FileSystemEventHandler _onChangedHandler = null; - private FileSystemEventHandler _onCreatedHandler = null; - private FileSystemEventHandler _onDeletedHandler = null; - private RenamedEventHandler _onRenamedHandler = null; - private ErrorEventHandler _onErrorHandler = null; + private FileSystemEventHandler? _onChangedHandler; + private FileSystemEventHandler? _onCreatedHandler; + private FileSystemEventHandler? _onDeletedHandler; + private RenamedEventHandler? _onRenamedHandler; + private ErrorEventHandler? _onErrorHandler; private const int c_notifyFiltersValidMask = (int)(NotifyFilters.Attributes | NotifyFilters.CreationTime | @@ -65,8 +65,10 @@ public partial class FileSystemWatcher : Component, ISupportInitialize static FileSystemWatcher() { int s_notifyFiltersValidMask = 0; +#pragma warning disable CS8605 // Unboxing a possibly null value foreach (int enumValue in Enum.GetValues(typeof(NotifyFilters))) s_notifyFiltersValidMask |= enumValue; +#pragma warning restore CS8605 Debug.Assert(c_notifyFiltersValidMask == s_notifyFiltersValidMask, "The NotifyFilters enum has changed. The c_notifyFiltersValidMask must be updated to reflect the values of the NotifyFilters enum."); } #endif @@ -265,7 +267,7 @@ public string Path /// /// Occurs when a file or directory in the specified is changed. /// - public event FileSystemEventHandler Changed + public event FileSystemEventHandler? Changed { add { @@ -280,7 +282,7 @@ public event FileSystemEventHandler Changed /// /// Occurs when a file or directory in the specified is created. /// - public event FileSystemEventHandler Created + public event FileSystemEventHandler? Created { add { @@ -295,7 +297,7 @@ public event FileSystemEventHandler Created /// /// Occurs when a file or directory in the specified is deleted. /// - public event FileSystemEventHandler Deleted + public event FileSystemEventHandler? Deleted { add { @@ -310,7 +312,7 @@ public event FileSystemEventHandler Deleted /// /// Occurs when the internal buffer overflows. /// - public event ErrorEventHandler Error + public event ErrorEventHandler? Error { add { @@ -326,7 +328,7 @@ public event ErrorEventHandler Error /// Occurs when a file or directory in the specified /// is renamed. /// - public event RenamedEventHandler Renamed + public event RenamedEventHandler? Renamed { add { @@ -417,7 +419,7 @@ private void NotifyInternalBufferOverflowEvent() private void NotifyRenameEventArgs(WatcherChangeTypes action, ReadOnlySpan name, ReadOnlySpan oldName) { // filter if there's no handler or neither new name or old name match a specified pattern - RenamedEventHandler handler = _onRenamedHandler; + RenamedEventHandler? handler = _onRenamedHandler; if (handler != null && (MatchPattern(name) || MatchPattern(oldName))) { @@ -425,7 +427,7 @@ private void NotifyRenameEventArgs(WatcherChangeTypes action, ReadOnlySpan } } - private FileSystemEventHandler GetHandler(WatcherChangeTypes changeType) + private FileSystemEventHandler? GetHandler(WatcherChangeTypes changeType) { switch (changeType) { @@ -446,7 +448,7 @@ private FileSystemEventHandler GetHandler(WatcherChangeTypes changeType) /// private void NotifyFileSystemEventArgs(WatcherChangeTypes changeType, ReadOnlySpan name) { - FileSystemEventHandler handler = GetHandler(changeType); + FileSystemEventHandler? handler = GetHandler(changeType); if (handler != null && MatchPattern(name.IsEmpty ? _directory : name)) { @@ -459,7 +461,7 @@ private void NotifyFileSystemEventArgs(WatcherChangeTypes changeType, ReadOnlySp /// private void NotifyFileSystemEventArgs(WatcherChangeTypes changeType, string name) { - FileSystemEventHandler handler = GetHandler(changeType); + FileSystemEventHandler? handler = GetHandler(changeType); if (handler != null && MatchPattern(string.IsNullOrEmpty(name) ? _directory : name)) { @@ -494,11 +496,11 @@ protected void OnDeleted(FileSystemEventArgs e) InvokeOn(e, _onDeletedHandler); } - private void InvokeOn(FileSystemEventArgs e, FileSystemEventHandler handler) + private void InvokeOn(FileSystemEventArgs e, FileSystemEventHandler? handler) { if (handler != null) { - ISynchronizeInvoke syncObj = SynchronizingObject; + ISynchronizeInvoke? syncObj = SynchronizingObject; if (syncObj != null && syncObj.InvokeRequired) syncObj.BeginInvoke(handler, new object[] { this, e }); else @@ -512,10 +514,10 @@ private void InvokeOn(FileSystemEventArgs e, FileSystemEventHandler handler) [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#", Justification = "Changing from protected to private would be a breaking change")] protected void OnError(ErrorEventArgs e) { - ErrorEventHandler handler = _onErrorHandler; + ErrorEventHandler? handler = _onErrorHandler; if (handler != null) { - ISynchronizeInvoke syncObj = SynchronizingObject; + ISynchronizeInvoke? syncObj = SynchronizingObject; if (syncObj != null && syncObj.InvokeRequired) syncObj.BeginInvoke(handler, new object[] { this, e }); else @@ -529,10 +531,10 @@ protected void OnError(ErrorEventArgs e) [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers", MessageId = "0#", Justification = "Changing from protected to private would be a breaking change")] protected void OnRenamed(RenamedEventArgs e) { - RenamedEventHandler handler = _onRenamedHandler; + RenamedEventHandler? handler = _onRenamedHandler; if (handler != null) { - ISynchronizeInvoke syncObj = SynchronizingObject; + ISynchronizeInvoke? syncObj = SynchronizingObject; if (syncObj != null && syncObj.InvokeRequired) syncObj.BeginInvoke(handler, new object[] { this, e }); else @@ -549,8 +551,8 @@ public WaitForChangedResult WaitForChanged(WatcherChangeTypes changeType, int ti // none is done here, either. var tcs = new TaskCompletionSource(); - FileSystemEventHandler fseh = null; - RenamedEventHandler reh = null; + FileSystemEventHandler? fseh = null; + RenamedEventHandler? reh = null; // Register the event handlers based on what events are desired. The full framework // doesn't register for the Error event, so this doesn't either. @@ -642,7 +644,7 @@ private void StartRaisingEventsIfNotDisposed() StartRaisingEvents(); } - public override ISite Site + public override ISite? Site { get { @@ -659,7 +661,7 @@ public override ISite Site } } - public ISynchronizeInvoke SynchronizingObject { get; set; } + public ISynchronizeInvoke? SynchronizingObject { get; set; } public void BeginInit() { diff --git a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/InternalBufferOverflowException.cs b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/InternalBufferOverflowException.cs index ae24374dcd0bd..ae74b5ca27634 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/InternalBufferOverflowException.cs +++ b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/InternalBufferOverflowException.cs @@ -24,7 +24,7 @@ public InternalBufferOverflowException() : base() /// /// Initializes a new instance of the class with the error message to be displayed specified. /// - public InternalBufferOverflowException(string message) : base(message) + public InternalBufferOverflowException(string? message) : base(message) { HResult = HResults.InternalBufferOverflow; } @@ -33,7 +33,7 @@ public InternalBufferOverflowException(string message) : base(message) /// Initializes a new instance of the /// class with the message to be displayed and the generated inner exception specified. /// - public InternalBufferOverflowException(string message, Exception inner) : base(message, inner) + public InternalBufferOverflowException(string? message, Exception? inner) : base(message, inner) { HResult = HResults.InternalBufferOverflow; } diff --git a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/RenamedEventArgs.cs b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/RenamedEventArgs.cs index fb9b0822834bb..e6b9a68a41d54 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/RenamedEventArgs.cs +++ b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/RenamedEventArgs.cs @@ -9,13 +9,13 @@ namespace System.IO /// public class RenamedEventArgs : FileSystemEventArgs { - private readonly string _oldName; + private readonly string? _oldName; private readonly string _oldFullPath; /// /// Initializes a new instance of the class. /// - public RenamedEventArgs(WatcherChangeTypes changeType, string directory, string name, string oldName) + public RenamedEventArgs(WatcherChangeTypes changeType, string directory, string? name, string? oldName) : base(changeType, directory, name) { _oldName = oldName; @@ -36,7 +36,7 @@ public string OldFullPath /// /// Gets the old name of the affected file or directory. /// - public string OldName + public string? OldName { get { diff --git a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/WaitForChangedResult.cs b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/WaitForChangedResult.cs index 7548cdfec85d2..da684ad87dc47 100644 --- a/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/WaitForChangedResult.cs +++ b/src/libraries/System.IO.FileSystem.Watcher/src/System/IO/WaitForChangedResult.cs @@ -6,7 +6,7 @@ namespace System.IO { public struct WaitForChangedResult { - internal WaitForChangedResult(WatcherChangeTypes changeType, string name, string oldName, bool timedOut) + internal WaitForChangedResult(WatcherChangeTypes changeType, string? name, string? oldName, bool timedOut) { ChangeType = changeType; Name = name; @@ -18,8 +18,8 @@ internal WaitForChangedResult(WatcherChangeTypes changeType, string name, string new WaitForChangedResult(changeType: 0, name: null, oldName: null, timedOut: true); public WatcherChangeTypes ChangeType { get; set; } - public string Name { get; set; } - public string OldName { get; set; } + public string? Name { get; set; } + public string? OldName { get; set; } public bool TimedOut { get; set; } } } diff --git a/src/libraries/System.IO.Ports/src/System.IO.Ports.csproj b/src/libraries/System.IO.Ports/src/System.IO.Ports.csproj index e8654bb022b21..cd787524d9a80 100644 --- a/src/libraries/System.IO.Ports/src/System.IO.Ports.csproj +++ b/src/libraries/System.IO.Ports/src/System.IO.Ports.csproj @@ -5,6 +5,7 @@ SR.PlatformNotSupported_IOPorts $(DefineConstants);NOSPAN true + annotations net461-Windows_NT-Debug;net461-Windows_NT-Release;netfx-Windows_NT-Debug;netfx-Windows_NT-Release;netstandard2.0-Debug;netstandard2.0-Linux-Debug;netstandard2.0-Linux-Release;netstandard2.0-OSX-Debug;netstandard2.0-OSX-Release;netstandard2.0-Release;netstandard2.0-Windows_NT-Debug;netstandard2.0-Windows_NT-Release