Skip to content
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

Different output in Unix between Path.GetPathRoot overloads for redundant segments #35260

Closed
carlossanlop opened this issue Apr 21, 2020 · 7 comments · Fixed by #35734
Closed
Assignees
Labels
area-System.IO bug os-linux Linux OS (any supported distro)
Milestone

Comments

@carlossanlop
Copy link
Member

carlossanlop commented Apr 21, 2020

While working on this issue (PR), I found a bug in the string overload for Path.GetPathRoot (Unix):

using System;
using System.IO;
namespace ConsoleAppCore
{
    class Program
    {
        static void Main()
        {
            string original = "/home/../folder/../..";
            string expected = "/";
            ReadOnlySpan<char> actualSpan = Path.GetPathRoot(original.AsSpan());
            string actualString = Path.GetPathRoot(original);
            Console.WriteLine($"Expected:      '{expected}'");
            Console.WriteLine($"Actual string: '{actualString}'");          // This returns '/'
            Console.WriteLine($"Actual span:   '{actualSpan.ToString()}'"); // This returns ''
        }
    }
}

I also found that running the same code in Windows will yield the behavior below, which is why I originally opened this issue, but @jkotas kindly pointed out it's expected behavior - the string method overload normalizes, while the span overload does not:

            Console.WriteLine($"Actual span:   '{actualSpan.ToString()}'"); // This returns '/'
            Console.WriteLine($"Actual string: '{actualString}'");          // This returns '\'
@carlossanlop carlossanlop added this to the 5.0 milestone Apr 21, 2020
@carlossanlop carlossanlop self-assigned this Apr 21, 2020
@ghost
Copy link

ghost commented Apr 21, 2020

Tagging subscribers to this area: @jozkee
Notify danmosemsft if you want to be subscribed.

@Dotnet-GitSync-Bot Dotnet-GitSync-Bot added the untriaged New issue has not been triaged by the area owner label Apr 21, 2020
@carlossanlop
Copy link
Member Author

Assigning to myself to investigate and fix since it's blocking my redundant segments PR.

@carlossanlop carlossanlop removed the untriaged New issue has not been triaged by the area owner label Apr 21, 2020
@jkotas
Copy link
Member

jkotas commented Apr 21, 2020

FWIW, this is by design. From https://docs.microsoft.com/en-us/dotnet/api/system.io.path.getpathroot?view=netcore-3.1#System_IO_Path_GetPathRoot_System_ReadOnlySpan_System_Char__ :

Unlike the string overload, this method doesn't normalize directory separators.

@carlossanlop
Copy link
Member Author

carlossanlop commented Apr 22, 2020

Thanks, @jkotas. I assume the behavior is different because we wanted to prevent introducing a breaking change in the old string overload method, and the new method had the behavior corrected to prevent normalization. I found a couple of places in our own code where the string overload of GetPathRoot is being used to normalize a path.

@carlossanlop
Copy link
Member Author

carlossanlop commented Apr 28, 2020

I'm reopening this issue because I reviewed my notes more closely and realized the actual bug is in Unix. I edited the original description with the details.

@carlossanlop carlossanlop reopened this Apr 28, 2020
@carlossanlop carlossanlop added the os-linux Linux OS (any supported distro) label Apr 28, 2020
@carlossanlop carlossanlop changed the title Different output between Path.GetPathRoot overloads for redundant segments Different output in Unix between Path.GetPathRoot overloads for redundant segments Apr 28, 2020
@carlossanlop
Copy link
Member Author

carlossanlop commented Apr 28, 2020

Found the root cause - The IsEffectivelyEmpty check is wrong:

public static ReadOnlySpan<char> GetPathRoot(ReadOnlySpan<char> path)
{
return PathInternal.IsEffectivelyEmpty(path) && IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString.AsSpan() : ReadOnlySpan<char>.Empty;
}

It should be inverted to match the same way we check for empty in the string overload:

public static string? GetPathRoot(string? path)
{
if (PathInternal.IsEffectivelyEmpty(path)) return null;
return IsPathRooted(path) ? PathInternal.DirectorySeparatorCharAsString : string.Empty;
}

I also noticed we do not have unit tests verifying this method. I'll include this fix in my PR for removing redundant segments, which also adds unit tests for the case that helped me find this problem.

@jkotas
Copy link
Member

jkotas commented Apr 28, 2020

I would be better to submit the fix for the bug and the associated test coverage in separate PR.

@ghost ghost locked as resolved and limited conversation to collaborators Dec 9, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.IO bug os-linux Linux OS (any supported distro)
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants