diff --git a/src/libraries/System.Console/src/Resources/Strings.resx b/src/libraries/System.Console/src/Resources/Strings.resx index 6f16c49d115b60..9e0151c9df0feb 100644 --- a/src/libraries/System.Console/src/Resources/Strings.resx +++ b/src/libraries/System.Console/src/Resources/Strings.resx @@ -182,9 +182,6 @@ The process cannot access the file because it is being used by another process. - - Probable I/O race condition detected while copying memory. The I/O package is not thread safe by default. In multithreaded applications, a stream must be accessed in a thread-safe way, such as a thread-safe wrapper returned by TextReader's or TextWriter's Synchronized methods. This also applies to classes like StreamWriter and StreamReader. - The ConsoleColor enum value was not defined on that enum. Please use a defined color from the enum. diff --git a/src/libraries/System.Console/src/System/ConsolePal.Android.cs b/src/libraries/System.Console/src/System/ConsolePal.Android.cs index 3586f64aff0765..910a33ad824949 100644 --- a/src/libraries/System.Console/src/System/ConsolePal.Android.cs +++ b/src/libraries/System.Console/src/System/ConsolePal.Android.cs @@ -11,11 +11,11 @@ internal sealed unsafe class LogcatStream : ConsoleStream { public LogcatStream() : base(FileAccess.Write) {} - public override int Read(byte[] buffer, int offset, int count) => throw Error.GetReadNotSupported(); + public override int Read(Span buffer) => throw Error.GetReadNotSupported(); - public override unsafe void Write(byte[] buffer, int offset, int count) + public override unsafe void Write(ReadOnlySpan buffer) { - string log = ConsolePal.OutputEncoding.GetString(buffer, offset, count); + string log = ConsolePal.OutputEncoding.GetString(buffer); Interop.Logcat.AndroidLogPrint(Interop.Logcat.LogLevel.Info, "DOTNET", log); } } diff --git a/src/libraries/System.Console/src/System/ConsolePal.Unix.cs b/src/libraries/System.Console/src/System/ConsolePal.Unix.cs index dd20b7040e9b85..9dec2e221a86bc 100644 --- a/src/libraries/System.Console/src/System/ConsolePal.Unix.cs +++ b/src/libraries/System.Console/src/System/ConsolePal.Unix.cs @@ -1212,15 +1212,13 @@ private void AddKey(TermInfo.Database db, string extendedName, ConsoleKey key, b /// Reads data from the file descriptor into the buffer. /// The file descriptor. /// The buffer to read into. - /// The offset at which to start writing into the buffer. - /// The maximum number of bytes to read. /// The number of bytes read, or a negative value if there's an error. - internal static unsafe int Read(SafeFileHandle fd, byte[] buffer, int offset, int count) + internal static unsafe int Read(SafeFileHandle fd, Span buffer) { fixed (byte* bufPtr = buffer) { - int result = Interop.CheckIo(Interop.Sys.Read(fd, (byte*)bufPtr + offset, count)); - Debug.Assert(result <= count); + int result = Interop.CheckIo(Interop.Sys.Read(fd, bufPtr, buffer.Length)); + Debug.Assert(result <= buffer.Length); return result; } } @@ -1424,26 +1422,13 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - public override int Read(byte[] buffer, int offset, int count) - { - ValidateRead(buffer, offset, count); - - if (_useReadLine) - { - return ConsolePal.StdInReader.ReadLine(buffer, offset, count); - } - else - { - return ConsolePal.Read(_handle, buffer, offset, count); - } - } + public override int Read(Span buffer) => + _useReadLine ? + ConsolePal.StdInReader.ReadLine(buffer) : + ConsolePal.Read(_handle, buffer); - public override void Write(byte[] buffer, int offset, int count) - { - ValidateWrite(buffer, offset, count); - - ConsolePal.Write(_handle, buffer.AsSpan(offset, count)); - } + public override void Write(ReadOnlySpan buffer) => + ConsolePal.Write(_handle, buffer); public override void Flush() { diff --git a/src/libraries/System.Console/src/System/ConsolePal.WebAssembly.cs b/src/libraries/System.Console/src/System/ConsolePal.WebAssembly.cs index cbce7776d5397d..405326185ebb7f 100644 --- a/src/libraries/System.Console/src/System/ConsolePal.WebAssembly.cs +++ b/src/libraries/System.Console/src/System/ConsolePal.WebAssembly.cs @@ -29,15 +29,13 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - public override int Read(byte[] buffer, int offset, int count) => throw Error.GetReadNotSupported(); + public override int Read(Span buffer) => throw Error.GetReadNotSupported(); - public override unsafe void Write(byte[] buffer, int offset, int count) + public override unsafe void Write(ReadOnlySpan buffer) { - ValidateWrite(buffer, offset, count); - fixed (byte* bufPtr = buffer) { - Write(_handle, bufPtr + offset, count); + Write(_handle, bufPtr, buffer.Length); } } diff --git a/src/libraries/System.Console/src/System/ConsolePal.Windows.cs b/src/libraries/System.Console/src/System/ConsolePal.Windows.cs index 76edfe8512e858..78758905ea6a54 100644 --- a/src/libraries/System.Console/src/System/ConsolePal.Windows.cs +++ b/src/libraries/System.Console/src/System/ConsolePal.Windows.cs @@ -1118,24 +1118,24 @@ protected override void Dispose(bool disposing) base.Dispose(disposing); } - public override int Read(byte[] buffer, int offset, int count) + public override int Read(Span buffer) { - ValidateRead(buffer, offset, count); - - int bytesRead; - int errCode = ReadFileNative(_handle, buffer, offset, count, _isPipe, out bytesRead, _useFileAPIs); + int errCode = ReadFileNative(_handle, buffer, _isPipe, out int bytesRead, _useFileAPIs); if (Interop.Errors.ERROR_SUCCESS != errCode) + { throw Win32Marshal.GetExceptionForWin32Error(errCode); + } + return bytesRead; } - public override void Write(byte[] buffer, int offset, int count) + public override void Write(ReadOnlySpan buffer) { - ValidateWrite(buffer, offset, count); - - int errCode = WriteFileNative(_handle, buffer, offset, count, _useFileAPIs); + int errCode = WriteFileNative(_handle, buffer, _useFileAPIs); if (Interop.Errors.ERROR_SUCCESS != errCode) + { throw Win32Marshal.GetExceptionForWin32Error(errCode); + } } public override void Flush() @@ -1149,35 +1149,26 @@ public override void Flush() // world working set and to avoid requiring a reference to the // System.IO.FileSystem contract. - private static unsafe int ReadFileNative(IntPtr hFile, byte[] bytes, int offset, int count, bool isPipe, out int bytesRead, bool useFileAPIs) + private static unsafe int ReadFileNative(IntPtr hFile, Span buffer, bool isPipe, out int bytesRead, bool useFileAPIs) { - Debug.Assert(offset >= 0, "offset >= 0"); - Debug.Assert(count >= 0, "count >= 0"); - Debug.Assert(bytes != null, "bytes != null"); - // Don't corrupt memory when multiple threads are erroneously writing - // to this stream simultaneously. - if (bytes.Length - offset < count) - throw new IndexOutOfRangeException(SR.IndexOutOfRange_IORaceCondition); - - // You can't use the fixed statement on an array of length 0. - if (bytes.Length == 0) + if (buffer.IsEmpty) { bytesRead = 0; return Interop.Errors.ERROR_SUCCESS; } bool readSuccess; - fixed (byte* p = &bytes[0]) + fixed (byte* p = buffer) { if (useFileAPIs) { - readSuccess = (0 != Interop.Kernel32.ReadFile(hFile, p + offset, count, out bytesRead, IntPtr.Zero)); + readSuccess = (0 != Interop.Kernel32.ReadFile(hFile, p, buffer.Length, out bytesRead, IntPtr.Zero)); } else { // If the code page could be Unicode, we should use ReadConsole instead, e.g. int charsRead; - readSuccess = Interop.Kernel32.ReadConsole(hFile, p + offset, count / BytesPerWChar, out charsRead, IntPtr.Zero); + readSuccess = Interop.Kernel32.ReadConsole(hFile, p, buffer.Length / BytesPerWChar, out charsRead, IntPtr.Zero); bytesRead = charsRead * BytesPerWChar; } } @@ -1193,24 +1184,19 @@ private static unsafe int ReadFileNative(IntPtr hFile, byte[] bytes, int offset, return errorCode; } - private static unsafe int WriteFileNative(IntPtr hFile, byte[] bytes, int offset, int count, bool useFileAPIs) + private static unsafe int WriteFileNative(IntPtr hFile, ReadOnlySpan bytes, bool useFileAPIs) { - Debug.Assert(offset >= 0, "offset >= 0"); - Debug.Assert(count >= 0, "count >= 0"); - Debug.Assert(bytes != null, "bytes != null"); - Debug.Assert(bytes.Length >= offset + count, "bytes.Length >= offset + count"); - // You can't use the fixed statement on an array of length 0. - if (bytes.Length == 0) + if (bytes.IsEmpty) return Interop.Errors.ERROR_SUCCESS; bool writeSuccess; - fixed (byte* p = &bytes[0]) + fixed (byte* p = bytes) { if (useFileAPIs) { int numBytesWritten; - writeSuccess = (0 != Interop.Kernel32.WriteFile(hFile, p + offset, count, out numBytesWritten, IntPtr.Zero)); + writeSuccess = (0 != Interop.Kernel32.WriteFile(hFile, p, bytes.Length, out numBytesWritten, IntPtr.Zero)); // In some cases we have seen numBytesWritten returned that is twice count; // so we aren't asserting the value of it. See https://github.com/dotnet/runtime/issues/23776 } @@ -1223,8 +1209,8 @@ private static unsafe int WriteFileNative(IntPtr hFile, byte[] bytes, int offset // However, we do not need to worry about that because the StreamWriter in Console has // a much shorter buffer size anyway. int charsWritten; - writeSuccess = Interop.Kernel32.WriteConsole(hFile, p + offset, count / BytesPerWChar, out charsWritten, IntPtr.Zero); - Debug.Assert(!writeSuccess || count / BytesPerWChar == charsWritten); + writeSuccess = Interop.Kernel32.WriteConsole(hFile, p, bytes.Length / BytesPerWChar, out charsWritten, IntPtr.Zero); + Debug.Assert(!writeSuccess || bytes.Length / BytesPerWChar == charsWritten); } } if (writeSuccess) diff --git a/src/libraries/System.Console/src/System/ConsolePal.iOS.cs b/src/libraries/System.Console/src/System/ConsolePal.iOS.cs index 089ebf24fcf9d5..fa1f2d120a8c88 100644 --- a/src/libraries/System.Console/src/System/ConsolePal.iOS.cs +++ b/src/libraries/System.Console/src/System/ConsolePal.iOS.cs @@ -10,15 +10,13 @@ internal sealed class NSLogStream : ConsoleStream { public NSLogStream() : base(FileAccess.Write) {} - public override int Read(byte[] buffer, int offset, int count) => throw Error.GetReadNotSupported(); + public override int Read(Span buffer) => throw Error.GetReadNotSupported(); - public override unsafe void Write(byte[] buffer, int offset, int count) + public override unsafe void Write(ReadOnlySpan buffer) { - ValidateWrite(buffer, offset, count); - fixed (byte* ptr = buffer) { - Interop.Sys.Log(ptr + offset, count); + Interop.Sys.Log(ptr, buffer.Length); } } } diff --git a/src/libraries/System.Console/src/System/IO/ConsoleStream.cs b/src/libraries/System.Console/src/System/IO/ConsoleStream.cs index 9fdc87da2f1d4a..935f565c7ed1d8 100644 --- a/src/libraries/System.Console/src/System/IO/ConsoleStream.cs +++ b/src/libraries/System.Console/src/System/IO/ConsoleStream.cs @@ -3,7 +3,6 @@ using System.Diagnostics; using System.Runtime.InteropServices; -using System.Text; namespace System.IO { @@ -21,37 +20,46 @@ internal ConsoleStream(FileAccess access) _canWrite = ((access & FileAccess.Write) == FileAccess.Write); } - protected override void Dispose(bool disposing) + public override void Write(byte[] buffer, int offset, int count) { - _canRead = false; - _canWrite = false; - base.Dispose(disposing); + ValidateWrite(buffer, offset, count); + Write(new ReadOnlySpan(buffer, offset, count)); } - public sealed override bool CanRead - { - get { return _canRead; } - } + public override void WriteByte(byte value) => Write(MemoryMarshal.CreateReadOnlySpan(ref value, 1)); - public sealed override bool CanWrite + public override int Read(byte[] buffer, int offset, int count) { - get { return _canWrite; } + ValidateRead(buffer, offset, count); + return Read(new Span(buffer, offset, count)); } - public sealed override bool CanSeek + public override int ReadByte() { - get { return false; } + byte b = 0; + int result = Read(MemoryMarshal.CreateSpan(ref b, 1)); + return result != 0 ? b : -1; } - public sealed override long Length + protected override void Dispose(bool disposing) { - get { throw Error.GetSeekNotSupported(); } + _canRead = false; + _canWrite = false; + base.Dispose(disposing); } + public sealed override bool CanRead => _canRead; + + public sealed override bool CanWrite => _canWrite; + + public sealed override bool CanSeek => false; + + public sealed override long Length => throw Error.GetSeekNotSupported(); + public sealed override long Position { - get { throw Error.GetSeekNotSupported(); } - set { throw Error.GetSeekNotSupported(); } + get => throw Error.GetSeekNotSupported(); + set => throw Error.GetSeekNotSupported(); } public override void Flush() @@ -59,28 +67,28 @@ public override void Flush() if (!CanWrite) throw Error.GetWriteNotSupported(); } - public sealed override void SetLength(long value) - { - throw Error.GetSeekNotSupported(); - } + public sealed override void SetLength(long value) => throw Error.GetSeekNotSupported(); - public sealed override long Seek(long offset, SeekOrigin origin) - { - throw Error.GetSeekNotSupported(); - } + public sealed override long Seek(long offset, SeekOrigin origin) => throw Error.GetSeekNotSupported(); protected void ValidateRead(byte[] buffer, int offset, int count) { ValidateBufferArguments(buffer, offset, count); - if (!_canRead) throw Error.GetReadNotSupported(); + if (!_canRead) + { + throw Error.GetReadNotSupported(); + } } protected void ValidateWrite(byte[] buffer, int offset, int count) { ValidateBufferArguments(buffer, offset, count); - if (!_canWrite) throw Error.GetWriteNotSupported(); + if (!_canWrite) + { + throw Error.GetWriteNotSupported(); + } } } } diff --git a/src/libraries/System.Console/src/System/IO/StdInReader.cs b/src/libraries/System.Console/src/System/IO/StdInReader.cs index 8b0cd2021ecfe9..7af76c629f0800 100644 --- a/src/libraries/System.Console/src/System/IO/StdInReader.cs +++ b/src/libraries/System.Console/src/System/IO/StdInReader.cs @@ -93,9 +93,9 @@ internal unsafe int ReadStdin(byte* buffer, int bufferSize) return line; } - public int ReadLine(byte[] buffer, int offset, int count) + public int ReadLine(Span buffer) { - if (count == 0) + if (buffer.IsEmpty) { return 0; } @@ -114,11 +114,10 @@ public int ReadLine(byte[] buffer, int offset, int count) Encoder encoder = _bufferReadEncoder ??= _encoding.GetEncoder(); int bytesUsedTotal = 0; int charsUsedTotal = 0; - Span destination = buffer.AsSpan(offset, count); foreach (ReadOnlyMemory chunk in _readLineSB.GetChunks()) { - encoder.Convert(chunk.Span, destination, flush: false, out int charsUsed, out int bytesUsed, out bool completed); - destination = destination.Slice(bytesUsed); + encoder.Convert(chunk.Span, buffer, flush: false, out int charsUsed, out int bytesUsed, out bool completed); + buffer = buffer.Slice(bytesUsed); bytesUsedTotal += bytesUsed; charsUsedTotal += charsUsed; diff --git a/src/libraries/System.Console/src/System/IO/SyncTextReader.Unix.cs b/src/libraries/System.Console/src/System/IO/SyncTextReader.Unix.cs index 9e68d74c1b0023..b2e5d36d7a23a4 100644 --- a/src/libraries/System.Console/src/System/IO/SyncTextReader.Unix.cs +++ b/src/libraries/System.Console/src/System/IO/SyncTextReader.Unix.cs @@ -40,7 +40,6 @@ public bool KeyAvailable } } - public int ReadLine(byte[] buffer, int offset, int count) - => Inner.ReadLine(buffer, offset, count); + public int ReadLine(Span buffer) => Inner.ReadLine(buffer); } } diff --git a/src/libraries/System.Console/src/System/TermInfo.cs b/src/libraries/System.Console/src/System/TermInfo.cs index d8e954d8c5e465..be84dbaf836619 100644 --- a/src/libraries/System.Console/src/System/TermInfo.cs +++ b/src/libraries/System.Console/src/System/TermInfo.cs @@ -262,10 +262,9 @@ private static bool TryOpen(string filePath, [NotNullWhen(true)] out SafeFileHan { throw new InvalidOperationException(SR.IO_TermInfoInvalid); } - int fileLen = (int)termInfoLength; - byte[] data = new byte[fileLen]; - if (ConsolePal.Read(fd, data, 0, fileLen) != fileLen) + byte[] data = new byte[(int)termInfoLength]; + if (ConsolePal.Read(fd, data) != data.Length) { throw new InvalidOperationException(SR.IO_TermInfoInvalid); }