summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJeremy Kuhne <jeremy.kuhne@microsoft.com>2018-02-22 12:13:59 -0800
committerGitHub <noreply@github.com>2018-02-22 12:13:59 -0800
commita0cfda07fbdc319d8e5137a1b28d190ec242597e (patch)
tree0a051e80cdd0c728b7b02440fcfd653cb1f207e2 /src
parent593782e030e803d93562ff802be984681959f2dd (diff)
downloadcoreclr-a0cfda07fbdc319d8e5137a1b28d190ec242597e.tar.gz
coreclr-a0cfda07fbdc319d8e5137a1b28d190ec242597e.tar.bz2
coreclr-a0cfda07fbdc319d8e5137a1b28d190ec242597e.zip
Remove most preemptive checks from GetFullPath (#16478)
* Removing the colon block that tosses out paths that are not device path. Fixes: #26359 * Striping 2nd and 3rd args of PathHelper.Normalize * Build on Maryam's changes - Remove all path validity checks outside of - Null - Embedded null - Empty or all spaces (effectively empty) - Remove PathStartSkip helper - Use span overloads for StringBuffer usage - Clean up some comments * Address feedback * Tweak to match other AsSpan methods
Diffstat (limited to 'src')
-rw-r--r--src/mscorlib/shared/System.Private.CoreLib.Shared.projitems1
-rw-r--r--src/mscorlib/shared/System/IO/Path.Windows.cs56
-rw-r--r--src/mscorlib/shared/System/IO/Path.cs36
-rw-r--r--src/mscorlib/shared/System/IO/PathHelper.Windows.cs146
-rw-r--r--src/mscorlib/shared/System/IO/PathInternal.Windows.StringBuffer.cs91
-rw-r--r--src/mscorlib/shared/System/IO/PathInternal.Windows.cs23
-rw-r--r--src/mscorlib/shared/System/IO/Win32Marshal.cs2
-rw-r--r--src/mscorlib/shared/System/Runtime/InteropServices/StringBuffer.cs3
8 files changed, 49 insertions, 309 deletions
diff --git a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems
index d0dc4394b4..0ce28f5cc0 100644
--- a/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems
+++ b/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems
@@ -717,7 +717,6 @@
<Compile Include="$(MSBuildThisFileDirectory)System\IO\Path.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\PathHelper.Windows.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\PathInternal.Windows.cs" />
- <Compile Include="$(MSBuildThisFileDirectory)System\IO\PathInternal.Windows.StringBuffer.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IO\DisableMediaInsertionPrompt.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Security\SafeBSTRHandle.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Security\SecureString.Windows.cs" />
diff --git a/src/mscorlib/shared/System/IO/Path.Windows.cs b/src/mscorlib/shared/System/IO/Path.Windows.cs
index b0443c5fd8..846dd53492 100644
--- a/src/mscorlib/shared/System/IO/Path.Windows.cs
+++ b/src/mscorlib/shared/System/IO/Path.Windows.cs
@@ -27,64 +27,32 @@ namespace System.IO
(char)31
};
- // Expands the given path to a fully qualified path.
+ // Expands the given path to a fully qualified path.
public static string GetFullPath(string path)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
- // Embedded null characters are the only invalid character case we want to check up front.
+ // If the path would normalize to string empty, we'll consider it empty
+ if (PathInternal.IsEffectivelyEmpty(path))
+ throw new ArgumentException(SR.Arg_PathEmpty, nameof(path));
+
+ // Embedded null characters are the only invalid character case we trully care about.
// This is because the nulls will signal the end of the string to Win32 and therefore have
- // unpredictable results. Other invalid characters we give a chance to be normalized out.
+ // unpredictable results.
if (path.IndexOf('\0') != -1)
throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
if (PathInternal.IsExtended(path))
{
- // We can't really know what is valid for all cases of extended paths.
- //
- // - object names can include other characters as well (':', '/', etc.)
- // - even file objects have different rules (pipe names can contain most characters)
- //
- // As such we will do no further analysis of extended paths to avoid blocking known and unknown
- // scenarios as well as minimizing compat breaks should we block now and need to unblock later.
+ // \\?\ paths are considered normalized by definition. Windows doesn't normalize \\?\
+ // paths and neither should we. Even if we wanted to GetFullPathName does not work
+ // properly with device paths. If one wants to pass a \\?\ path through normalization
+ // one can chop off the prefix, pass it to GetFullPath and add it again.
return path;
}
- bool isDevice = PathInternal.IsDevice(path);
- if (!isDevice)
- {
- // Toss out paths with colons that aren't a valid drive specifier.
- // Cannot start with a colon and can only be of the form "C:".
- // (Note that we used to explicitly check "http:" and "file:"- these are caught by this check now.)
- int startIndex = PathInternal.PathStartSkip(path);
-
- // Move past the colon
- startIndex += 2;
-
- if ((path.Length > 0 && path[0] == PathInternal.VolumeSeparatorChar)
- || (path.Length >= startIndex && path[startIndex - 1] == PathInternal.VolumeSeparatorChar && !PathInternal.IsValidDriveChar(path[startIndex - 2]))
- || (path.Length > startIndex && path.IndexOf(PathInternal.VolumeSeparatorChar, startIndex) != -1))
- {
- throw new NotSupportedException(SR.Format(SR.Argument_PathFormatNotSupported_Path, path));
- }
- }
-
- // Technically this doesn't matter but we used to throw for this case
- if (PathInternal.IsEffectivelyEmpty(path))
- throw new ArgumentException(SR.Arg_PathEmpty, nameof(path));
-
- // We don't want to check invalid characters for device format- see comments for extended above
- string fullPath = PathHelper.Normalize(path, checkInvalidCharacters: !isDevice, expandShortPaths: true);
-
- if (!isDevice)
- {
- // Emulate FileIOPermissions checks, retained for compatibility (normal invalid characters have already been checked)
- if (PathInternal.HasWildCardCharacters(fullPath))
- throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
- }
-
- return fullPath;
+ return PathHelper.Normalize(path);
}
public static string GetFullPath(string path, string basePath)
diff --git a/src/mscorlib/shared/System/IO/Path.cs b/src/mscorlib/shared/System/IO/Path.cs
index 079eab27f8..3784b877d3 100644
--- a/src/mscorlib/shared/System/IO/Path.cs
+++ b/src/mscorlib/shared/System/IO/Path.cs
@@ -119,10 +119,12 @@ namespace System.IO
return end;
}
- // Returns the extension of the given path. The returned value includes the
- // period (".") character of the extension except when you have a terminal period when you get string.Empty, such as ".exe" or
- // ".cpp". The returned value is null if the given path is
- // null or if the given path does not include an extension.
+ /// <summary>
+ /// Returns the extension of the given path. The returned value includes the period (".") character of the
+ /// extension except when you have a terminal period when you get string.Empty, such as ".exe" or ".cpp".
+ /// The returned value is null if the given path is null or empty if the given path does not include an
+ /// extension.
+ /// </summary>
public static string GetExtension(string path)
{
if (path == null)
@@ -157,9 +159,11 @@ namespace System.IO
return ReadOnlySpan<char>.Empty;
}
- // Returns the name and extension parts of the given path. The resulting
- // string contains the characters of path that follow the last
- // separator in path. The resulting string is null if path is null.
+ /// <summary>
+ /// Returns the name and extension parts of the given path. The resulting string contains
+ /// the characters of path that follow the last separator in path. The resulting string is
+ /// null if path is null.
+ /// </summary>
public static string GetFileName(string path)
{
if (path == null)
@@ -215,8 +219,10 @@ namespace System.IO
fileName.Slice(0, lastPeriod);
}
- // Returns a cryptographically strong random 8.3 string that can be
- // used as either a folder name or a file name.
+ /// <summary>
+ /// Returns a cryptographically strong random 8.3 string that can be
+ /// used as either a folder name or a file name.
+ /// </summary>
public static unsafe string GetRandomFileName()
{
byte* pKey = stackalloc byte[KeyLength];
@@ -256,10 +262,11 @@ namespace System.IO
return !PathInternal.IsPartiallyQualified(path);
}
- // Tests if a path includes a file extension. The result is
- // true if the characters that follow the last directory
- // separator ('\\' or '/') or volume separator (':') in the path include
- // a period (".") other than a terminal period. The result is false otherwise.
+
+ /// <summary>
+ /// Tests if a path's file name includes a file extension. A trailing period
+ /// is not considered an extension.
+ /// </summary>
public static bool HasExtension(string path)
{
if (path != null)
@@ -801,9 +808,6 @@ namespace System.IO
return StringBuilderCache.GetStringAndRelease(sb);
}
- // StringComparison and IsCaseSensitive are also available in PathInternal.CaseSensitivity but we are
- // too low in System.Runtime.Extensions to use it (no FileStream, etc.)
-
/// <summary>Returns a comparison that can be used to compare file and directory names for equality.</summary>
internal static StringComparison StringComparison
{
diff --git a/src/mscorlib/shared/System/IO/PathHelper.Windows.cs b/src/mscorlib/shared/System/IO/PathHelper.Windows.cs
index 6aa01d0495..bde11fd423 100644
--- a/src/mscorlib/shared/System/IO/PathHelper.Windows.cs
+++ b/src/mscorlib/shared/System/IO/PathHelper.Windows.cs
@@ -3,7 +3,6 @@
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace System.IO
@@ -13,136 +12,26 @@ namespace System.IO
/// </summary>
internal class PathHelper
{
- // Can't be over 8.3 and be a short name
- private const int MaxShortName = 12;
-
- private const char LastAnsi = (char)255;
- private const char Delete = (char)127;
-
/// <summary>
/// Normalize the given path.
/// </summary>
/// <remarks>
- /// Normalizes via Win32 GetFullPathName(). Will also trim initial
- /// spaces if the path is determined to be rooted.
- ///
- /// Note that invalid characters will be checked after the path is normalized, which could remove bad characters. (C:\|\..\a.txt -- C:\a.txt)
+ /// Normalizes via Win32 GetFullPathName().
/// </remarks>
/// <param name="path">Path to normalize</param>
- /// <param name="checkInvalidCharacters">True to check for invalid characters</param>
- /// <param name="expandShortPaths">Attempt to expand short paths if true</param>
- /// <exception cref="ArgumentException">Thrown if the path is an illegal UNC (does not contain a full server/share) or contains illegal characters.</exception>
- /// <exception cref="PathTooLongException">Thrown if the path or a path segment exceeds the filesystem limits.</exception>
- /// <exception cref="FileNotFoundException">Thrown if Windows returns ERROR_FILE_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error)</exception>
- /// <exception cref="DirectoryNotFoundException">Thrown if Windows returns ERROR_PATH_NOT_FOUND. (See Win32Marshal.GetExceptionForWin32Error)</exception>
- /// <exception cref="UnauthorizedAccessException">Thrown if Windows returns ERROR_ACCESS_DENIED. (See Win32Marshal.GetExceptionForWin32Error)</exception>
- /// <exception cref="IOException">Thrown if Windows returns an error that doesn't map to the above. (See Win32Marshal.GetExceptionForWin32Error)</exception>
+ /// <exception cref="PathTooLongException">Thrown if we have a string that is too large to fit into a UNICODE_STRING.</exception>
+ /// <exception cref="IOException">Thrown if the path is empty.</exception>
/// <returns>Normalized path</returns>
- internal static string Normalize(string path, bool checkInvalidCharacters, bool expandShortPaths)
+ internal static string Normalize(string path)
{
// Get the full path
- StringBuffer fullPath = new StringBuffer(PathInternal.MaxShortPath);
+ StringBuffer fullPath = new StringBuffer(path.Length);
try
{
GetFullPathName(path, ref fullPath);
- // Checking path validity used to happen before getting the full path name. To avoid additional input allocation
- // (to trim trailing whitespace) we now do it after the Win32 call. This will allow legitimate paths through that
- // used to get kicked back (notably segments with invalid characters might get removed via "..").
- //
- // There is no way that GetLongPath can invalidate the path so we'll do this (cheaper) check before we attempt to
- // expand short file names.
-
- // Scan the path for:
- //
- // - Illegal path characters.
- // - Invalid UNC paths like \\, \\server, \\server\.
-
- // As the path could be > 30K, we'll combine the validity scan. None of these checks are performed by the Win32
- // GetFullPathName() API.
-
- bool possibleShortPath = false;
- bool foundTilde = false;
-
- // We can get UNCs as device paths through this code (e.g. \\.\UNC\), we won't validate them as there isn't
- // an easy way to normalize without extensive cost (we'd have to hunt down the canonical name for any device
- // path that contains UNC or to see if the path was doing something like \\.\GLOBALROOT\Device\Mup\,
- // \\.\GLOBAL\UNC\, \\.\GLOBALROOT\GLOBAL??\UNC\, etc.
- bool specialPath = fullPath.Length > 1 && fullPath[0] == '\\' && fullPath[1] == '\\';
- bool isDevice = PathInternal.IsDevice(ref fullPath);
- bool possibleBadUnc = specialPath && !isDevice;
- int index = specialPath ? 2 : 0;
- int lastSeparator = specialPath ? 1 : 0;
- int segmentLength;
- char current;
-
- while (index < fullPath.Length)
- {
- current = fullPath[index];
-
- // Try to skip deeper analysis. '?' and higher are valid/ignorable except for '\', '|', and '~'
- if (current < '?' || current == '\\' || current == '|' || current == '~')
- {
- switch (current)
- {
- case '|':
- case '>':
- case '<':
- case '\"':
- if (checkInvalidCharacters) throw new ArgumentException(SR.Argument_InvalidPathChars);
- foundTilde = false;
- break;
- case '~':
- foundTilde = true;
- break;
- case '\\':
- segmentLength = index - lastSeparator - 1;
- lastSeparator = index;
-
- if (foundTilde)
- {
- if (segmentLength <= MaxShortName)
- {
- // Possibly a short path.
- possibleShortPath = true;
- }
-
- foundTilde = false;
- }
-
- if (possibleBadUnc)
- {
- // If we're at the end of the path and this is the first separator, we're missing the share.
- // Otherwise we're good, so ignore UNC tracking from here.
- if (index == fullPath.Length - 1)
- throw new ArgumentException(SR.Format(SR.Arg_PathIllegalUNC_Path, fullPath.ToString()));
- else
- possibleBadUnc = false;
- }
-
- break;
-
- default:
- if (checkInvalidCharacters && current < ' ') throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
- break;
- }
- }
-
- index++;
- }
-
- if (possibleBadUnc)
- throw new ArgumentException(SR.Format(SR.Arg_PathIllegalUNC_Path, fullPath.ToString()));
-
- segmentLength = fullPath.Length - lastSeparator - 1;
-
- if (foundTilde && segmentLength <= MaxShortName)
- possibleShortPath = true;
-
- // Check for a short filename path and try and expand it. Technically you don't need to have a tilde for a short name, but
- // this is how we've always done this. This expansion is costly so we'll continue to let other short paths slide.
- if (expandShortPaths && possibleShortPath)
+ if (fullPath.Contains('~'))
{
return TryExpandShortFileName(ref fullPath, originalPath: path);
}
@@ -150,13 +39,10 @@ namespace System.IO
{
if (fullPath.Length == path.Length && fullPath.StartsWith(path))
{
- // If we have the exact same string we were passed in, don't bother to allocate another string from the StringBuilder.
+ // If we have the exact same string we were passed in, don't bother to allocate another string from the StringBuffer.
return path;
}
- else
- {
- return fullPath.ToString();
- }
+ return fullPath.ToString();
}
}
finally
@@ -166,12 +52,6 @@ namespace System.IO
}
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsDosUnc(ref StringBuffer buffer)
- {
- return !PathInternal.IsDevice(ref buffer) && buffer.Length > 1 && buffer[0] == '\\' && buffer[1] == '\\';
- }
-
private static unsafe void GetFullPathName(string path, ref StringBuffer fullPath)
{
// If the string starts with an extended prefix we would need to remove it from the path before we call GetFullPathName as
@@ -239,7 +119,7 @@ namespace System.IO
// We guarantee we'll expand short names for paths that only partially exist. As such, we need to find the part of the path that actually does exist. To
// avoid allocating like crazy we'll create only one input array and modify the contents with embedded nulls.
- Debug.Assert(!PathInternal.IsPartiallyQualified(ref outputBuffer), "should have resolved by now");
+ Debug.Assert(!PathInternal.IsPartiallyQualified(outputBuffer.AsSpan()), "should have resolved by now");
// We'll have one of a few cases by now (the normalized path will have already:
//
@@ -251,8 +131,8 @@ namespace System.IO
//
// Note that we will never get \??\ here as GetFullPathName() does not recognize \??\ and will return it as C:\??\ (or whatever the current drive is).
- int rootLength = PathInternal.GetRootLength(ref outputBuffer);
- bool isDevice = PathInternal.IsDevice(ref outputBuffer);
+ int rootLength = PathInternal.GetRootLength(outputBuffer.AsSpan());
+ bool isDevice = PathInternal.IsDevice(outputBuffer.AsSpan());
StringBuffer inputBuffer = new StringBuffer(0);
try
@@ -275,7 +155,7 @@ namespace System.IO
}
else
{
- isDosUnc = IsDosUnc(ref outputBuffer);
+ isDosUnc = !PathInternal.IsDevice(outputBuffer.AsSpan()) && outputBuffer.Length > 1 && outputBuffer[0] == '\\' && outputBuffer[1] == '\\';
rootDifference = GetInputBuffer(ref outputBuffer, isDosUnc, ref inputBuffer);
}
@@ -345,7 +225,7 @@ namespace System.IO
string returnValue = null;
- int newLength = (int)(bufferToUse.Length - rootDifference);
+ int newLength = bufferToUse.Length - rootDifference;
if (isDosUnc)
{
// Need to go from \\?\UNC\ to \\?\UN\\
diff --git a/src/mscorlib/shared/System/IO/PathInternal.Windows.StringBuffer.cs b/src/mscorlib/shared/System/IO/PathInternal.Windows.StringBuffer.cs
deleted file mode 100644
index ea132423b2..0000000000
--- a/src/mscorlib/shared/System/IO/PathInternal.Windows.StringBuffer.cs
+++ /dev/null
@@ -1,91 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Diagnostics;
-using System.Runtime.InteropServices;
-
-namespace System.IO
-{
- /// <summary>Contains internal path helpers that are shared between many projects.</summary>
- internal static partial class PathInternal
- {
- /// <summary>
- /// Returns true if the path uses the extended syntax (\\?\)
- /// </summary>
- internal static bool IsExtended(ref StringBuffer path)
- {
- // While paths like "//?/C:/" will work, they're treated the same as "\\.\" paths.
- // Skipping of normalization will *only* occur if back slashes ('\') are used.
- return path.Length >= DevicePrefixLength
- && path[0] == '\\'
- && (path[1] == '\\' || path[1] == '?')
- && path[2] == '?'
- && path[3] == '\\';
- }
-
- /// <summary>
- /// Gets the length of the root of the path (drive, share, etc.).
- /// </summary>
- internal static int GetRootLength(ref StringBuffer path)
- {
- if (path.Length == 0)
- return 0;
-
- return GetRootLength(new ReadOnlySpan<char>(path.UnderlyingArray, 0, path.Length));
- }
-
- /// <summary>
- /// Returns true if the path uses any of the DOS device path syntaxes. ("\\.\", "\\?\", or "\??\")
- /// </summary>
- internal static bool IsDevice(ref StringBuffer path)
- {
- // If the path begins with any two separators is will be recognized and normalized and prepped with
- // "\??\" for internal usage correctly. "\??\" is recognized and handled, "/??/" is not.
- return IsExtended(ref path)
- ||
- (
- path.Length >= DevicePrefixLength
- && IsDirectorySeparator(path[0])
- && IsDirectorySeparator(path[1])
- && (path[2] == '.' || path[2] == '?')
- && IsDirectorySeparator(path[3])
- );
- }
-
- /// <summary>
- /// Returns true if the path specified is relative to the current drive or working directory.
- /// Returns false if the path is fixed to a specific drive or UNC path. This method does no
- /// validation of the path (URIs will be returned as relative as a result).
- /// </summary>
- /// <remarks>
- /// Handles paths that use the alternate directory separator. It is a frequent mistake to
- /// assume that rooted paths (Path.IsPathRooted) are not relative. This isn't the case.
- /// "C:a" is drive relative- meaning that it will be resolved against the current directory
- /// for C: (rooted, but relative). "C:\a" is rooted and not relative (the current directory
- /// will not be used to modify the path).
- /// </remarks>
- internal static bool IsPartiallyQualified(ref StringBuffer path)
- {
- if (path.Length < 2)
- {
- // It isn't fixed, it must be relative. There is no way to specify a fixed
- // path with one character (or less).
- return true;
- }
-
- if (IsDirectorySeparator(path[0]))
- {
- // There is no valid way to specify a relative path with two initial slashes or
- // \? as ? isn't valid for drive relative paths and \??\ is equivalent to \\?\
- return !(path[1] == '?' || IsDirectorySeparator(path[1]));
- }
-
- // The only way to specify a fixed path that doesn't begin with two slashes
- // is the drive, colon, slash format- i.e. C:\
- return !((path.Length >= 3)
- && (path[1] == VolumeSeparatorChar)
- && IsDirectorySeparator(path[2]));
- }
- }
-}
diff --git a/src/mscorlib/shared/System/IO/PathInternal.Windows.cs b/src/mscorlib/shared/System/IO/PathInternal.Windows.cs
index d4d5917bba..95bd0625d8 100644
--- a/src/mscorlib/shared/System/IO/PathInternal.Windows.cs
+++ b/src/mscorlib/shared/System/IO/PathInternal.Windows.cs
@@ -275,29 +275,6 @@ namespace System.IO
}
/// <summary>
- /// Returns the characters to skip at the start of the path if it starts with space(s) and a drive or directory separator.
- /// (examples are " C:", " \")
- /// This is a legacy behavior of Path.GetFullPath().
- /// </summary>
- /// <remarks>
- /// Note that this conflicts with IsPathRooted() which doesn't (and never did) such a skip.
- /// </remarks>
- internal static int PathStartSkip(ReadOnlySpan<char> path)
- {
- int startIndex = 0;
- while (startIndex < path.Length && path[startIndex] == ' ') startIndex++;
-
- if (startIndex > 0 && (startIndex < path.Length && IsDirectorySeparator(path[startIndex]))
- || (startIndex + 1 < path.Length && path[startIndex + 1] == ':' && IsValidDriveChar(path[startIndex])))
- {
- // Go ahead and skip spaces as we're either " C:" or " \"
- return startIndex;
- }
-
- return 0;
- }
-
- /// <summary>
/// True if the given character is a directory separator.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/src/mscorlib/shared/System/IO/Win32Marshal.cs b/src/mscorlib/shared/System/IO/Win32Marshal.cs
index 14a064a700..0a8f2dca0c 100644
--- a/src/mscorlib/shared/System/IO/Win32Marshal.cs
+++ b/src/mscorlib/shared/System/IO/Win32Marshal.cs
@@ -69,7 +69,7 @@ namespace System.IO
return new PathTooLongException(SR.Format(SR.IO_PathTooLong_Path, path));
case Interop.Errors.ERROR_INVALID_DRIVE:
- throw new DriveNotFoundException(SR.Format(SR.IO_DriveNotFound_Drive, path));
+ throw new DriveNotFoundException(SR.Format(SR.IO_DriveNotFound_Drive, path));
case Interop.Errors.ERROR_INVALID_PARAMETER:
return new IOException(Interop.Kernel32.GetMessage(errorCode), MakeHRFromErrorCode(errorCode));
diff --git a/src/mscorlib/shared/System/Runtime/InteropServices/StringBuffer.cs b/src/mscorlib/shared/System/Runtime/InteropServices/StringBuffer.cs
index fdd0b95590..42bf9cc7b7 100644
--- a/src/mscorlib/shared/System/Runtime/InteropServices/StringBuffer.cs
+++ b/src/mscorlib/shared/System/Runtime/InteropServices/StringBuffer.cs
@@ -297,5 +297,8 @@ namespace System.Runtime.InteropServices
_buffer = null;
_length = 0;
}
+
+ public ReadOnlySpan<char> AsSpan()
+ => new ReadOnlySpan<char>(UnderlyingArray, 0, Length);
}
}