Annotate System.IO.FileSystem.Watcher for nullable reference types (#456)
authorStephen Toub <stoub@microsoft.com>
Tue, 3 Dec 2019 15:28:54 +0000 (10:28 -0500)
committerGitHub <noreply@github.com>
Tue, 3 Dec 2019 15:28:54 +0000 (10:28 -0500)
14 files changed:
src/libraries/Common/src/System/IO/PathInternal.Windows.cs
src/libraries/Common/src/System/Text/ValueUtf8Converter.cs
src/libraries/System.IO.FileSystem.Watcher/ref/System.IO.FileSystem.Watcher.cs
src/libraries/System.IO.FileSystem.Watcher/ref/System.IO.FileSystem.Watcher.csproj
src/libraries/System.IO.FileSystem.Watcher/src/System.IO.FileSystem.Watcher.csproj
src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemEventArgs.cs
src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Linux.cs
src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.OSX.cs
src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.Win32.cs
src/libraries/System.IO.FileSystem.Watcher/src/System/IO/FileSystemWatcher.cs
src/libraries/System.IO.FileSystem.Watcher/src/System/IO/InternalBufferOverflowException.cs
src/libraries/System.IO.FileSystem.Watcher/src/System/IO/RenamedEventArgs.cs
src/libraries/System.IO.FileSystem.Watcher/src/System/IO/WaitForChangedResult.cs
src/libraries/System.IO.Ports/src/System.IO.Ports.csproj

index 1287b56..c7fcd25 100644 (file)
@@ -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 @@ namespace System.IO
         /// 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)
         /// </summary>
-        /// TODO: add atribute [return: NotNullIfNotNull("path")]
+        [return: NotNullIfNotNull("path")]
         internal static string? EnsureExtendedPrefixIfNeeded(string? path)
         {
             if (path != null && (path.Length >= MaxShortPath || EndsWithPeriodOrSpace(path)))
index 3a2ba29..7d8aaae 100644 (file)
@@ -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
     /// </summary>
     internal ref struct ValueUtf8Converter
     {
-        private byte[] _arrayToReturnToPool;
+        private byte[]? _arrayToReturnToPool;
         private Span<byte> _bytes;
 
         public ValueUtf8Converter(Span<byte> initialBuffer)
@@ -40,7 +41,7 @@ namespace System.Text
 
         public void Dispose()
         {
-            byte[] toReturn = _arrayToReturnToPool;
+            byte[]? toReturn = _arrayToReturnToPool;
             if (toReturn != null)
             {
                 _arrayToReturnToPool = null;
index 95ecae6..86a892b 100644 (file)
@@ -15,10 +15,10 @@ namespace System.IO
     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 @@ namespace System.IO
         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 @@ namespace System.IO
     {
         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 @@ namespace System.IO
     }
     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 @@ namespace System.IO
         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]
index 56d3123..c0ad246 100644 (file)
@@ -1,5 +1,6 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
+    <Nullable>enable</Nullable>
     <Configurations>netcoreapp-Debug;netcoreapp-Release</Configurations>
   </PropertyGroup>
   <ItemGroup>
index 6fb40ad..154aea6 100644 (file)
@@ -1,6 +1,7 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
+    <Nullable>enable</Nullable>
     <Configurations>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</Configurations>
   </PropertyGroup>
   <ItemGroup>
index 8df624b..84c963d 100644 (file)
@@ -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;
 
         /// <devdoc>
         /// Initializes a new instance of the <see cref='System.IO.FileSystemEventArgs'/> class.
         /// </devdoc>
-        public FileSystemEventArgs(WatcherChangeTypes changeType, string directory, string name)
+        public FileSystemEventArgs(WatcherChangeTypes changeType, string directory, string? name)
         {
             _changeType = changeType;
             _name = name;
@@ -31,7 +31,7 @@ namespace System.IO
         /// This is like Path.Combine, except without argument validation,
         /// and a separator is used even if the name argument is empty.
         /// </remarks>
-        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 @@ namespace System.IO
         /// <devdoc>
         ///       Gets the name of the affected file or directory.
         /// </devdoc>
-        public string Name
+        public string? Name
         {
             get
             {
index 25789da..def4ae5 100644 (file)
@@ -40,7 +40,7 @@ namespace System.IO
                 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 @@ namespace System.IO
         /// Cancellation for the currently running watch operation.
         /// This is non-null if an operation has been started and null if stopped.
         /// </summary>
-        private CancellationTokenSource _cancellation;
+        private CancellationTokenSource? _cancellation;
 
         /// <summary>Reads the value of a max user limit path from procfs.</summary>
         /// <param name="path">The path to read.</param>
         /// <returns>The value read, or "0" if a failure occurred.</returns>
-        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 @@ namespace System.IO
             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 @@ namespace System.IO
             /// <summary>Adds a watch on a directory to the existing inotify handle.</summary>
             /// <param name="parent">The parent directory entry.</param>
             /// <param name="directoryName">The new directory path to monitor, relative to the root.</param>
-            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 @@ namespace System.IO
                     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 @@ namespace System.IO
                         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 @@ namespace System.IO
                 }
 
                 // 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 @@ namespace System.IO
                     // 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 @@ namespace System.IO
                 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 @@ namespace System.IO
                 // 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<char> previousEventName = ReadOnlySpan<char>.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 @@ namespace System.IO
                         // 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 @@ namespace System.IO
 
                         uint mask = nextEvent.mask;
                         ReadOnlySpan<char> expandedName = ReadOnlySpan<char>.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 @@ namespace System.IO
                 }
                 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 @@ namespace System.IO
             {
                 /// <summary>A StringBuilder cached on the current thread to avoid allocations when possible.</summary>
                 [ThreadStatic]
-                private static StringBuilder t_builder;
+                private static StringBuilder? t_builder;
 
                 /// <summary>The parent directory.</summary>
-                internal WatchedDirectory Parent;
+                internal WatchedDirectory? Parent;
 
                 /// <summary>The watch descriptor associated with this directory.</summary>
                 internal int WatchDescriptor;
 
                 /// <summary>The filename of this directory.</summary>
-                internal string Name;
+                internal string? Name;
 
                 /// <summary>Child directories of this directory for which we added explicit watches.</summary>
-                internal List<WatchedDirectory> Children;
+                internal List<WatchedDirectory>? Children;
 
                 /// <summary>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.</summary>
-                internal List<WatchedDirectory> InitializedChildren
-                {
-                    get
-                    {
-                        if (Children == null)
-                        {
-                            Children = new List<WatchedDirectory> ();
-                        }
-                        return Children;
-                    }
-                }
+                internal List<WatchedDirectory> InitializedChildren => Children ??= new List<WatchedDirectory>();
 
                 // 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 @@ namespace System.IO
                 /// <param name="relativeToRoot">Whether to get a path relative to the root directory being watched, or a full path.</param>
                 /// <param name="additionalName">An additional name to include in the path, relative to this directory.</param>
                 /// <returns>The computed path.</returns>
-                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
index 77a9209..f5e2d88 100644 (file)
@@ -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 @@ namespace System.IO
             if (IsSuspended())
                 return;
 
-            CancellationTokenSource token = _cancellation;
+            CancellationTokenSource? token = _cancellation;
             if (token != null)
             {
                 _cancellation = null;
@@ -80,7 +79,7 @@ namespace System.IO
         // ---- PAL layer ends here ----
         // -----------------------------
 
-        private CancellationTokenSource _cancellation;
+        private CancellationTokenSource? _cancellation;
 
         private static FSEventStreamEventFlags TranslateFlags(NotifyFilters flagsToTranslate)
         {
@@ -141,11 +140,11 @@ namespace System.IO
             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 @@ namespace System.IO
             // 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 @@ namespace System.IO
                 _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 @@ namespace System.IO
                         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 @@ namespace System.IO
                     }
                 }
 
-                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 @@ namespace System.IO
                     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 @@ namespace System.IO
 
             private void CancellationCallback()
             {
-                SafeEventStreamHandle eventStream =  _eventStream;
+                SafeEventStreamHandle? eventStream =  _eventStream;
                 if (!_stopping && eventStream != null)
                 {
                     _stopping = true;
@@ -343,8 +345,7 @@ namespace System.IO
                 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 @@ namespace System.IO
                 // 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 @@ namespace System.IO
                 {
                     ExecutionContext.Run(
                         context,
-                        (object o) => ((RunningInstance)o).ProcessEvents(numEvents.ToInt32(), eventPaths, new Span<FSEventStreamEventFlags>(eventFlags, numEvents.ToInt32()), new Span<FSEventStreamEventId>(eventIds, numEvents.ToInt32()), watcher),
+                        (object? o) => ((RunningInstance)o!).ProcessEvents(numEvents.ToInt32(), eventPaths, new Span<FSEventStreamEventFlags>(eventFlags, numEvents.ToInt32()), new Span<FSEventStreamEventId>(eventIds, numEvents.ToInt32()), watcher),
                         this);
                 }
             }
index ceeca50..8df1800 100644 (file)
@@ -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 @@ namespace System.IO
                 {
                     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 @@ namespace System.IO
         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 @@ namespace System.IO
         /// </summary>
         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 @@ namespace System.IO
             internal byte[] Buffer { get; }
             internal SafeFileHandle DirectoryHandle { get; }
             internal ThreadPoolBoundHandle ThreadPoolBinding { get; }
-            internal PreAllocatedOverlapped PreAllocatedOverlapped { get; set;  }
+            internal PreAllocatedOverlapped? PreAllocatedOverlapped { get; set;  }
             internal WeakReference<FileSystemWatcher> WeakWatcher { get; }
         }
     }
index f52beaf..79e3c5e 100644 (file)
@@ -46,11 +46,11 @@ namespace System.IO
         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 @@ namespace System.IO
         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 @@ namespace System.IO
         /// <devdoc>
         ///    Occurs when a file or directory in the specified <see cref='System.IO.FileSystemWatcher.Path'/> is changed.
         /// </devdoc>
-        public event FileSystemEventHandler Changed
+        public event FileSystemEventHandler? Changed
         {
             add
             {
@@ -280,7 +282,7 @@ namespace System.IO
         /// <devdoc>
         ///    Occurs when a file or directory in the specified <see cref='System.IO.FileSystemWatcher.Path'/> is created.
         /// </devdoc>
-        public event FileSystemEventHandler Created
+        public event FileSystemEventHandler? Created
         {
             add
             {
@@ -295,7 +297,7 @@ namespace System.IO
         /// <devdoc>
         ///    Occurs when a file or directory in the specified <see cref='System.IO.FileSystemWatcher.Path'/> is deleted.
         /// </devdoc>
-        public event FileSystemEventHandler Deleted
+        public event FileSystemEventHandler? Deleted
         {
             add
             {
@@ -310,7 +312,7 @@ namespace System.IO
         /// <devdoc>
         ///    Occurs when the internal buffer overflows.
         /// </devdoc>
-        public event ErrorEventHandler Error
+        public event ErrorEventHandler? Error
         {
             add
             {
@@ -326,7 +328,7 @@ namespace System.IO
         ///    Occurs when a file or directory in the specified <see cref='System.IO.FileSystemWatcher.Path'/>
         ///    is renamed.
         /// </devdoc>
-        public event RenamedEventHandler Renamed
+        public event RenamedEventHandler? Renamed
         {
             add
             {
@@ -417,7 +419,7 @@ namespace System.IO
         private void NotifyRenameEventArgs(WatcherChangeTypes action, ReadOnlySpan<char> name, ReadOnlySpan<char> 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 @@ namespace System.IO
             }
         }
 
-        private FileSystemEventHandler GetHandler(WatcherChangeTypes changeType)
+        private FileSystemEventHandler? GetHandler(WatcherChangeTypes changeType)
         {
             switch (changeType)
             {
@@ -446,7 +448,7 @@ namespace System.IO
         /// </summary>
         private void NotifyFileSystemEventArgs(WatcherChangeTypes changeType, ReadOnlySpan<char> name)
         {
-            FileSystemEventHandler handler = GetHandler(changeType);
+            FileSystemEventHandler? handler = GetHandler(changeType);
 
             if (handler != null && MatchPattern(name.IsEmpty ? _directory : name))
             {
@@ -459,7 +461,7 @@ namespace System.IO
         /// </summary>
         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 @@ namespace System.IO
             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 @@ namespace System.IO
         [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 @@ namespace System.IO
         [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 @@ namespace System.IO
             // none is done here, either.
 
             var tcs = new TaskCompletionSource<WaitForChangedResult>();
-            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 @@ namespace System.IO
             StartRaisingEvents();
         }
 
-        public override ISite Site
+        public override ISite? Site
         {
             get
             {
@@ -659,7 +661,7 @@ namespace System.IO
             }
         }
 
-        public ISynchronizeInvoke SynchronizingObject { get; set; }
+        public ISynchronizeInvoke? SynchronizingObject { get; set; }
 
         public void BeginInit()
         {
index ae24374..ae74b5c 100644 (file)
@@ -24,7 +24,7 @@ namespace System.IO
         /// <devdoc>
         ///    Initializes a new instance of the <see cref='System.IO.InternalBufferOverflowException'/> class with the error message to be displayed specified.
         /// </devdoc>
-        public InternalBufferOverflowException(string message) : base(message)
+        public InternalBufferOverflowException(string? message) : base(message)
         {
             HResult = HResults.InternalBufferOverflow;
         }
@@ -33,7 +33,7 @@ namespace System.IO
         ///    Initializes a new instance of the <see cref='System.IO.InternalBufferOverflowException'/>
         ///    class with the message to be displayed and the generated inner exception specified.
         /// </devdoc>
-        public InternalBufferOverflowException(string message, Exception inner) : base(message, inner)
+        public InternalBufferOverflowException(string? message, Exception? inner) : base(message, inner)
         {
             HResult = HResults.InternalBufferOverflow;
         }
index fb9b082..e6b9a68 100644 (file)
@@ -9,13 +9,13 @@ namespace System.IO
     /// </devdoc>
     public class RenamedEventArgs : FileSystemEventArgs
     {
-        private readonly string _oldName;
+        private readonly string? _oldName;
         private readonly string _oldFullPath;
 
         /// <devdoc>
         ///    Initializes a new instance of the <see cref='System.IO.RenamedEventArgs'/> class.
         /// </devdoc>
-        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 @@ namespace System.IO
         /// <devdoc>
         ///    Gets the old name of the affected file or directory.
         /// </devdoc>
-        public string OldName
+        public string? OldName
         {
             get
             {
index 7548cdf..da684ad 100644 (file)
@@ -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 @@ namespace System.IO
             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; }
     }
 }
index e8654bb..cd78752 100644 (file)
@@ -5,6 +5,7 @@
     <GeneratePlatformNotSupportedAssemblyMessage Condition="'$(TargetsNetStandard)' == 'true' and '$(OSGroup)' == 'AnyOS'">SR.PlatformNotSupported_IOPorts</GeneratePlatformNotSupportedAssemblyMessage>
     <DefineConstants>$(DefineConstants);NOSPAN</DefineConstants>
     <IncludeDllSafeSearchPathAttribute>true</IncludeDllSafeSearchPathAttribute>
+    <Nullable>annotations</Nullable>
     <Configurations>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</Configurations>
   </PropertyGroup>
   <ItemGroup Condition="'$(TargetsNetStandard)' == 'true' and '$(OSGroup)' != 'AnyOS'">