Previously the logic that turned "a/b/c/../.." into "a/" failed to
preserve an empty path at the end of the iteration sequence, as required
by the trailing slash. That meant the result didn't meet the class
invariants, and that "a/b/c/d/../../.." would remove four components
instead of the three that "../../.." should remove.

        PR libstdc++/87116
        * src/filesystem/std-path.cc (path::lexically_normal): When handling
        a dot-dot filename, preserve an empty final component in the iteration
        sequence.
        [_GLIBCXX_FILESYSTEM_IS_WINDOWS]: Use preferred-separator for
        root-directory.
        * testsuite/27_io/filesystem/path/generation/normal.cc: Add new tests
        for more than two adjacent dot-dot filenames.
        [_GLIBCXX_FILESYSTEM_IS_WINDOWS]: Replace slashes with
        preferred-separator in expected normalized strings.

Tested x86_64-linux and x86_64-w64-mingw32.


commit e98e02af564a5836c885eec607f1a288d69e16c8
Author: Jonathan Wakely <jwak...@redhat.com>
Date:   Tue Aug 28 13:55:09 2018 +0100

    PR libstdc++/87116 fix path::lexically_normal() handling of dot-dot
    
    Previously the logic that turned "a/b/c/../.." into "a/" failed to
    preserve an empty path at the end of the iteration sequence, as required
    by the trailing slash. That meant the result didn't meet the class
    invariants, and that "a/b/c/d/../../.." would remove four components
    instead of the three that "../../.." should remove.
    
            PR libstdc++/87116
            * src/filesystem/std-path.cc (path::lexically_normal): When handling
            a dot-dot filename, preserve an empty final component in the 
iteration
            sequence.
            [_GLIBCXX_FILESYSTEM_IS_WINDOWS]: Use preferred-separator for
            root-directory.
            * testsuite/27_io/filesystem/path/generation/normal.cc: Add new 
tests
            for more than two adjacent dot-dot filenames.
            [_GLIBCXX_FILESYSTEM_IS_WINDOWS]: Replace slashes with
            preferred-separator in expected normalized strings.

diff --git a/libstdc++-v3/src/filesystem/std-path.cc 
b/libstdc++-v3/src/filesystem/std-path.cc
index f6c0b8bb0f6..f382eb3759a 100644
--- a/libstdc++-v3/src/filesystem/std-path.cc
+++ b/libstdc++-v3/src/filesystem/std-path.cc
@@ -438,7 +438,7 @@ path::lexically_normal() const
     {
 #ifdef _GLIBCXX_FILESYSTEM_IS_WINDOWS
       // Replace each slash character in the root-name
-      if (p._M_type == _Type::_Root_name)
+      if (p._M_type == _Type::_Root_name || p._M_type == _Type::_Root_dir)
        {
          string_type s = p.native();
          std::replace(s.begin(), s.end(), L'/', L'\\');
@@ -458,7 +458,8 @@ path::lexically_normal() const
            }
          else if (!ret.has_relative_path())
            {
-             if (!ret.is_absolute())
+             // remove a dot-dot filename immediately after root-directory
+             if (!ret.has_root_directory())
                ret /= p;
            }
          else
@@ -471,8 +472,18 @@ path::lexically_normal() const
                {
                  // Remove the filename before the trailing slash
                  // (equiv. to ret = ret.parent_path().remove_filename())
-                 ret._M_pathname.erase(elem._M_cur->_M_pos);
-                 ret._M_cmpts.erase(elem._M_cur, ret._M_cmpts.end());
+
+                 if (elem == ret.begin())
+                   ret.clear();
+                 else
+                   {
+                     ret._M_pathname.erase(elem._M_cur->_M_pos);
+                     // Do we still have a trailing slash?
+                     if (std::prev(elem)->_M_type == _Type::_Filename)
+                       ret._M_cmpts.erase(elem._M_cur);
+                     else
+                       ret._M_cmpts.erase(elem._M_cur, ret._M_cmpts.end());
+                   }
                }
              else // ???
                ret /= p;
diff --git a/libstdc++-v3/testsuite/27_io/filesystem/path/generation/normal.cc 
b/libstdc++-v3/testsuite/27_io/filesystem/path/generation/normal.cc
index 6d0e2007a75..3b8311f81ad 100644
--- a/libstdc++-v3/testsuite/27_io/filesystem/path/generation/normal.cc
+++ b/libstdc++-v3/testsuite/27_io/filesystem/path/generation/normal.cc
@@ -24,7 +24,17 @@
 #include <testsuite_hooks.h>
 
 using std::filesystem::path;
-using __gnu_test::compare_paths;
+
+void
+compare_paths(path p, std::string expected)
+{
+#if defined(_WIN32) && !defined(__CYGWIN__)
+  for (auto& c : expected)
+    if (c == '/')
+      c = '\\';
+#endif
+  __gnu_test::compare_paths(p, expected);
+}
 
 void
 test01()
@@ -69,8 +79,11 @@ test03()
     {"/foo"        , "/foo" },
     {"/foo/"       , "/foo/" },
     {"/foo/."      , "/foo/" },
-    {"/foo/bar/.." , "/foo/" },
     {"/foo/.."     , "/" },
+    {"/foo/../.."  , "/" },
+    {"/foo/bar/.." , "/foo/" },
+    {"/foo/bar/../.." , "/" },
+    {"/foo/bar/baz/../../.." , "/" }, // PR libstdc++/87116
 
     {"/."          , "/" },
     {"/./"         , "/" },
@@ -88,10 +101,11 @@ test03()
     {"foo/.."      , "." },
     {"foo/../"     , "." },
     {"foo/../.."   , ".." },
+    {"foo/../../..", "../.." },
 
     // with root name (OS-dependent):
 #if defined(_WIN32) && !defined(__CYGWIN__)
-    {"C:bar/.."    , "C:." },
+    {"C:bar/.."    , "C:" },
 #else
     {"C:bar/.."    , "." },
 #endif
@@ -119,10 +133,53 @@ test03()
     compare_paths( path(test.input).lexically_normal(), test.normalized );
 }
 
+void
+test04()
+{
+  // PR libstdc++/87116
+  path p = "a/b/c";
+  compare_paths( (p/"../..").lexically_normal(), "a/" );
+
+  p = "a/b/c/d/e";
+  compare_paths( (p/"..").lexically_normal(),  "a/b/c/d/" );
+  compare_paths( (p/"../..").lexically_normal(),  "a/b/c/" );
+  compare_paths( (p/"../../..").lexically_normal(),  "a/b/" );
+  compare_paths( (p/"../../../..").lexically_normal(),  "a/" );
+  compare_paths( (p/"../../../../..").lexically_normal(),  "." );
+  compare_paths( (p/"../../../../../..").lexically_normal(),  ".." );
+
+  p = "/a/b/c/d/e";
+  compare_paths( (p/"..").lexically_normal(),  "/a/b/c/d/" );
+  compare_paths( (p/"../..").lexically_normal(),  "/a/b/c/" );
+  compare_paths( (p/"../../..").lexically_normal(),  "/a/b/" );
+  compare_paths( (p/"../../../..").lexically_normal(),  "/a/" );
+  compare_paths( (p/"../../../../..").lexically_normal(),  "/" );
+  compare_paths( (p/"../../../../../..").lexically_normal(),  "/" );
+
+#if defined(_WIN32) && !defined(__CYGWIN__)
+  p = "A:b/c/d/e";
+  compare_paths( (p/"..").lexically_normal(),  "A:b/c/d/" );
+  compare_paths( (p/"../..").lexically_normal(),  "A:b/c/" );
+  compare_paths( (p/"../../..").lexically_normal(),  "A:b/" );
+  compare_paths( (p/"../../../..").lexically_normal(),  "A:" );
+  compare_paths( (p/"../../../../..").lexically_normal(),  "A:.." );
+  compare_paths( (p/"../../../../../..").lexically_normal(),  "A:../.." );
+
+  p = "A:/b/c/d/e";
+  compare_paths( (p/"..").lexically_normal(),  "A:/b/c/d/" );
+  compare_paths( (p/"../..").lexically_normal(),  "A:/b/c/" );
+  compare_paths( (p/"../../..").lexically_normal(),  "A:/b/" );
+  compare_paths( (p/"../../../..").lexically_normal(),  "A:/" );
+  compare_paths( (p/"../../../../..").lexically_normal(),  "A:/" );
+  compare_paths( (p/"../../../../../..").lexically_normal(),  "A:/" );
+#endif
+}
+
 int
 main()
 {
   test01();
   test02();
   test03();
+  test04();
 }

Reply via email to