-
Notifications
You must be signed in to change notification settings - Fork 4.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Duplicated path.Contains('\0') check #54993
Comments
I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label. |
Tagging subscribers to this area: @dotnet/area-system-io Issue DetailsDescription
The method calls Discovered in #54253 Perhaps there are other places where this expensive check is duplicated but I did not find after a quick search.
|
Hey everyone. I did a quick research and came up with a very straightforward solution like refactoring
with some parts of the method. Before doing that, I'd like to ask you if it is ok. I'm concerned because it would lead to too much of copypaste code. There is another option like to create a private method that would be based on GetFullPath(string path) and use it in
@adamsitnik @jozkee @jeffschwMSFT What is your opinion on that? |
@iSazonov @SkiFoD I did some simple refactoring, no research needed. I didn't unit test it since it was just refactoring. If it's okay, I'll submit a PR. // Expands the given path to a fully qualified path.
public static string GetFullPath(string path)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
// If the path would normalize to string empty, we'll consider it empty
if (PathInternal.IsEffectivelyEmpty(path.AsSpan()))
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.
if (path.Contains('\0'))
throw new ArgumentException(SR.Argument_InvalidPathChars, nameof(path));
return GetFullQualifiedPath(path);
}
public static string GetFullPath(string path, string basePath)
{
if (path == null)
throw new ArgumentNullException(nameof(path));
if (basePath == null)
throw new ArgumentNullException(nameof(basePath));
if (!IsPathFullyQualified(basePath))
throw new ArgumentException(SR.Arg_BasePathNotFullyQualified, nameof(basePath));
if (basePath.Contains('\0') || path.Contains('\0'))
throw new ArgumentException(SR.Argument_InvalidPathChars);
if (IsPathFullyQualified(path))
return GetFullQualifiedPath(path);
if (PathInternal.IsEffectivelyEmpty(path.AsSpan()))
return basePath;
int length = path.Length;
string combinedPath;
if (length >= 1 && PathInternal.IsDirectorySeparator(path[0]))
{
// Path is current drive rooted i.e. starts with \:
// "\Foo" and "C:\Bar" => "C:\Foo"
// "\Foo" and "\\?\C:\Bar" => "\\?\C:\Foo"
combinedPath = Join(GetPathRoot(basePath.AsSpan()), path.AsSpan(1)); // Cut the separator to ensure we don't end up with two separators when joining with the root.
}
else if (length >= 2 && PathInternal.IsValidDriveChar(path[0]) && path[1] == PathInternal.VolumeSeparatorChar)
{
// Drive relative paths
Debug.Assert(length == 2 || !PathInternal.IsDirectorySeparator(path[2]));
if (GetVolumeName(path.AsSpan()).EqualsOrdinal(GetVolumeName(basePath.AsSpan())))
{
// Matching root
// "C:Foo" and "C:\Bar" => "C:\Bar\Foo"
// "C:Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo"
combinedPath = Join(basePath.AsSpan(), path.AsSpan(2));
}
else
{
// No matching root, root to specified drive
// "D:Foo" and "C:\Bar" => "D:Foo"
// "D:Foo" and "\\?\C:\Bar" => "\\?\D:\Foo"
combinedPath = !PathInternal.IsDevice(basePath.AsSpan())
? path.Insert(2, @"\")
: length == 2
? JoinInternal(basePath.AsSpan(0, 4), path.AsSpan(), @"\".AsSpan())
: JoinInternal(basePath.AsSpan(0, 4), path.AsSpan(0, 2), @"\".AsSpan(), path.AsSpan(2));
}
}
else
{
// "Simple" relative path
// "Foo" and "C:\Bar" => "C:\Bar\Foo"
// "Foo" and "\\?\C:\Bar" => "\\?\C:\Bar\Foo"
combinedPath = JoinInternal(basePath.AsSpan(), path.AsSpan());
}
// Device paths are normalized by definition, so passing something of this format (i.e. \\?\C:\.\tmp, \\.\C:\foo)
// to Windows APIs won't do anything by design. Additionally, GetFullPathName() in Windows doesn't root
// them properly. As such we need to manually remove segments and not use GetFullPath().
return PathInternal.IsDevice(combinedPath.AsSpan())
? PathInternal.RemoveRelativeSegments(combinedPath, PathInternal.GetRootLength(combinedPath.AsSpan()))
: GetFullQualifiedPath(combinedPath);
}
private static string GetFullQualifiedPath(string path)
{
if (PathInternal.IsExtended(path.AsSpan()))
{
// \\?\ 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;
}
return PathHelper.Normalize(path);
} |
@jeffhandley It seems there are a lot of places where the extra check presents. |
@iSazonov Do you suggest that we reopen this issue for follow-up to find and eliminate the other occurrences? Did you already identify some other places that could be noted? |
I am aware of some of the other locations of this check. I could submit a PR. |
Great; thanks. I'll reopen this issue then. It would be helpful to capture the identified locations here and we can turn them into a task list in the issue description. Thank you both! |
@jeffhandley could you assign this to me so I can track it in my todos? Thanks |
Description
runtime/src/libraries/System.Private.CoreLib/src/System/IO/Path.Windows.cs
Line 65 in 57bfe47
The method calls
public static string GetFullPath(string path)
wherepath.Contains('\0')
is called again.Discovered in #54253
Perhaps there are other places where this expensive check is duplicated but I did not find after a quick search.
The text was updated successfully, but these errors were encountered: