[PATCH] PR libstdc++/82777 fix path normalization for dot-dot

Jonathan Wakely jwakely@redhat.com
Wed Nov 1 17:13:00 GMT 2017


This fixes a bug in the implementation of the normalization algo. I
messed up a case with adjacent ../.. elements, because it's written to
work in a single pass, rather than applying each of the rules in turn,
creating loads of temporaries and reallocations.

	PR libstdc++/82777
	* src/filesystem/std-path.cc (path::lexically_normal): Remove dot-dot
	elements correctly.
	* testsuite/27_io/filesystem/path/generation/normal.cc: Add testcase.
	* testsuite/util/testsuite_fs.h (compare_paths): Improve exception
	text.

Tested powerpc64le-linux, committed to trunk.

-------------- next part --------------
commit a7c9222208d1b56118b3a6bd844c7511fc0d9377
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Wed Nov 1 16:07:56 2017 +0000

    PR libstdc++/82777 fix path normalization for dot-dot
    
            PR libstdc++/82777
            * src/filesystem/std-path.cc (path::lexically_normal): Remove dot-dot
            elements correctly.
            * testsuite/27_io/filesystem/path/generation/normal.cc: Add testcase.
            * testsuite/util/testsuite_fs.h (compare_paths): Improve exception
            text.

diff --git a/libstdc++-v3/src/filesystem/std-path.cc b/libstdc++-v3/src/filesystem/std-path.cc
index 1e2a8fad584..330aee72b13 100644
--- a/libstdc++-v3/src/filesystem/std-path.cc
+++ b/libstdc++-v3/src/filesystem/std-path.cc
@@ -388,10 +388,35 @@ path::lexically_normal() const
 #endif
       if (is_dotdot(p))
 	{
-	  if (ret.has_filename() && !is_dotdot(ret.filename()))
-	    ret.remove_filename();
-	  else if (ret.has_filename() || !ret.has_root_directory())
-	    ret /= p;
+	  if (ret.has_filename())
+	    {
+	      // remove a non-dot-dot filename immediately followed by /..
+	      if (!is_dotdot(ret.filename()))
+		ret.remove_filename();
+	      else
+		ret /= p;
+	    }
+	  else if (!ret.has_relative_path())
+	    {
+	      if (!ret.is_absolute())
+		ret /= p;
+	    }
+	  else
+	    {
+	      // Got a path with a relative path (i.e. at least one non-root
+	      // element) and no filename at the end (i.e. empty last element),
+	      // so must have a trailing slash. See what is before it.
+	      auto elem = std::prev(ret.end(), 2);
+	      if (elem->has_filename() && !is_dotdot(*elem))
+		{
+		  // 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());
+		}
+	      else // ???
+		ret /= p;
+	    }
 	}
       else if (is_dot(p))
 	ret /= path();
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 789ce186f82..df3b7154ab3 100644
--- a/libstdc++-v3/testsuite/27_io/filesystem/path/generation/normal.cc
+++ b/libstdc++-v3/testsuite/27_io/filesystem/path/generation/normal.cc
@@ -46,6 +46,10 @@ test02()
   compare_paths( path().lexically_normal(), "" );
 
   compare_paths( path("/..").lexically_normal(), "/" );
+
+  // PR libstdc++/82777
+  compare_paths( path("./a/b/c/../.././b/c").lexically_normal(), "a/b/c" );
+  compare_paths( path("/a/b/c/../.././b/c").lexically_normal(), "/a/b/c" );
 }
 
 void
diff --git a/libstdc++-v3/testsuite/util/testsuite_fs.h b/libstdc++-v3/testsuite/util/testsuite_fs.h
index 47f56090b47..c18dae28fcc 100644
--- a/libstdc++-v3/testsuite/util/testsuite_fs.h
+++ b/libstdc++-v3/testsuite/util/testsuite_fs.h
@@ -40,7 +40,7 @@ namespace __gnu_test
 {
 #define PATH_CHK(p1, p2, fn) \
     if ( p1.fn() != p2.fn() ) \
-      throw test_fs::filesystem_error( #fn, p1, p2, \
+      throw test_fs::filesystem_error("comparing '" #fn "' failed", p1, p2, \
 	  std::make_error_code(std::errc::invalid_argument) )
 
   void


More information about the Libstdc++ mailing list