[PATCH] Fix handling of an empty filename at end of a path

Jonathan Wakely jwakely@redhat.com
Wed May 23 16:10:00 GMT 2018


The C++17 std::filesystem::path grammar allows an empty filename as the
last component (to signify a trailing slash). The existing code does not
handle this consistently, sometimes an empty filename has type _Multi
and sometimes it has type _Filename. This can result in a non-empty
iterator range for an empty filename component.

This change ensures that empty paths always have type _Filename and will
yield an empty iterator range.

	* include/bits/fs_path.h (path::_M_type): Change default member
	initializer to _Filename.
	(path::begin): Create past-the-end iterator for empty path.
	* src/filesystem/std-path.cc (path::remove_filename()): Remove
	debugging check.
	(path::has_relative_path()): Return false for empty filenames.
	(path::_M_split_cmpts): Set _M_type to _Filename for empty paths.
	Fix offset of empty final component.
	* testsuite/27_io/filesystem/path/itr/components.cc: New.
	* testsuite/27_io/filesystem/path/itr/traversal.cc: Add new inputs.

Tested powerpc64le-linux, committed to trunk. I plan to backport this
to gcc-8-branch too.


-------------- next part --------------
commit ce947893c8e04ab3cde5f5dd3364a94a068b313c
Author: Jonathan Wakely <jwakely@redhat.com>
Date:   Wed May 23 14:42:08 2018 +0100

    Fix handling of an empty filename at end of a path
    
    The C++17 std::filesystem::path grammar allows an empty filename as the
    last component (to signify a trailing slash). The existing code does not
    handle this consistently, sometimes an empty filename has type _Multi
    and sometimes it has type _Filename. This can result in a non-empty
    iterator range for an empty filename component.
    
    This change ensures that empty paths always have type _Filename and will
    yield an empty iterator range.
    
            * include/bits/fs_path.h (path::_M_type): Change default member
            initializer to _Filename.
            (path::begin): Create past-the-end iterator for empty path.
            * src/filesystem/std-path.cc (path::remove_filename()): Remove
            debugging check.
            (path::has_relative_path()): Return false for empty filenames.
            (path::_M_split_cmpts): Set _M_type to _Filename for empty paths.
            Fix offset of empty final component.
            * testsuite/27_io/filesystem/path/itr/components.cc: New.
            * testsuite/27_io/filesystem/path/itr/traversal.cc: Add new inputs.

diff --git a/libstdc++-v3/include/bits/fs_path.h b/libstdc++-v3/include/bits/fs_path.h
index 51af2891647..79a341830db 100644
--- a/libstdc++-v3/include/bits/fs_path.h
+++ b/libstdc++-v3/include/bits/fs_path.h
@@ -497,7 +497,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
     struct _Cmpt;
     using _List = _GLIBCXX_STD_C::vector<_Cmpt>;
     _List _M_cmpts; // empty unless _M_type == _Type::_Multi
-    _Type _M_type = _Type::_Multi;
+    _Type _M_type = _Type::_Filename;
   };
 
   template<>
@@ -1076,7 +1076,7 @@ _GLIBCXX_BEGIN_NAMESPACE_CXX11
   {
     if (_M_type == _Type::_Multi)
       return iterator(this, _M_cmpts.begin());
-    return iterator(this, false);
+    return iterator(this, empty());
   }
 
   inline path::iterator
diff --git a/libstdc++-v3/src/filesystem/std-path.cc b/libstdc++-v3/src/filesystem/std-path.cc
index 6f594cec1d5..755cb7c883a 100644
--- a/libstdc++-v3/src/filesystem/std-path.cc
+++ b/libstdc++-v3/src/filesystem/std-path.cc
@@ -63,8 +63,6 @@ path::remove_filename()
     }
   else if (_M_type == _Type::_Filename)
     clear();
-  if (!empty() && _M_pathname.back() != '/')
-    throw 1;
   return *this;
 }
 
@@ -292,7 +290,7 @@ path::has_root_path() const
 bool
 path::has_relative_path() const
 {
-  if (_M_type == _Type::_Filename)
+  if (_M_type == _Type::_Filename && !_M_pathname.empty())
     return true;
   if (!_M_cmpts.empty())
     {
@@ -301,7 +299,7 @@ path::has_relative_path() const
         ++__it;
       if (__it != _M_cmpts.end() && __it->_M_type == _Type::_Root_dir)
         ++__it;
-      if (__it != _M_cmpts.end())
+      if (__it != _M_cmpts.end() && !__it->_M_pathname.empty())
         return true;
     }
   return false;
@@ -514,11 +512,13 @@ path::_M_find_extension() const
 void
 path::_M_split_cmpts()
 {
-  _M_type = _Type::_Multi;
   _M_cmpts.clear();
-
   if (_M_pathname.empty())
-    return;
+    {
+      _M_type = _Type::_Filename;
+      return;
+    }
+  _M_type = _Type::_Multi;
 
   size_t pos = 0;
   const size_t len = _M_pathname.size();
@@ -593,8 +593,7 @@ path::_M_split_cmpts()
       // An empty element, if trailing non-root directory-separator present.
       if (_M_cmpts.back()._M_type == _Type::_Filename)
 	{
-	  const auto& last = _M_cmpts.back();
-	  pos = last._M_pos + last._M_pathname.size();
+	  pos = _M_pathname.size();
 	  _M_cmpts.emplace_back(string_type(), _Type::_Filename, pos);
 	}
     }
diff --git a/libstdc++-v3/testsuite/27_io/filesystem/path/itr/components.cc b/libstdc++-v3/testsuite/27_io/filesystem/path/itr/components.cc
new file mode 100644
index 00000000000..15736b22fd5
--- /dev/null
+++ b/libstdc++-v3/testsuite/27_io/filesystem/path/itr/components.cc
@@ -0,0 +1,51 @@
+// Copyright (C) 2018 Free Software Foundation, Inc.
+//
+// This file is part of the GNU ISO C++ Library.  This library is free
+// software; you can redistribute it and/or modify it under the
+// terms of the GNU General Public License as published by the
+// Free Software Foundation; either version 3, or (at your option)
+// any later version.
+
+// This library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License along
+// with this library; see the file COPYING3.  If not see
+// <http://www.gnu.org/licenses/>.
+
+// { dg-options "-std=gnu++17 -lstdc++fs" }
+// { dg-do run { target c++17 } }
+// { dg-require-filesystem-ts "" }
+
+#include <filesystem>
+#include <iterator>
+#include <testsuite_hooks.h>
+#include <testsuite_fs.h>
+
+void
+test01()
+{
+  for (std::filesystem::path p : __gnu_test::test_paths)
+  {
+    if (p.empty())
+      VERIFY(std::distance(p.begin(), p.end()) == 0);
+    else
+      VERIFY(std::distance(p.begin(), p.end()) != 0);
+
+    for (const std::filesystem::path& cmpt : p)
+    {
+      if (cmpt.empty())
+	VERIFY(std::distance(cmpt.begin(), cmpt.end()) == 0);
+      else
+	VERIFY(std::distance(cmpt.begin(), cmpt.end()) == 1);
+    }
+  }
+}
+
+int
+main()
+{
+  test01();
+}
diff --git a/libstdc++-v3/testsuite/27_io/filesystem/path/itr/traversal.cc b/libstdc++-v3/testsuite/27_io/filesystem/path/itr/traversal.cc
index 3fc60d48d13..c23cb14fd73 100644
--- a/libstdc++-v3/testsuite/27_io/filesystem/path/itr/traversal.cc
+++ b/libstdc++-v3/testsuite/27_io/filesystem/path/itr/traversal.cc
@@ -104,7 +104,7 @@ test02()
 void
 test03()
 {
-  path paths[] = { "single", "multiple/elements" };
+  path paths[] = { "single", "multiple/elements", "trailing/slash/", "/." };
   for (const path& p : paths)
     for (auto iter = p.begin(); iter != p.end(); ++iter)
     {


More information about the Libstdc++ mailing list