Index: llvm/trunk/lib/Support/Path.cpp =================================================================== --- llvm/trunk/lib/Support/Path.cpp +++ llvm/trunk/lib/Support/Path.cpp @@ -91,10 +91,9 @@ return path.substr(0, end); } + // Returns the first character of the filename in str. For paths ending in + // '/', it returns the position of the '/'. size_t filename_pos(StringRef str, Style style) { - if (str.size() == 2 && is_separator(str[0], style) && str[0] == str[1]) - return 0; - if (str.size() > 0 && is_separator(str[str.size() - 1], style)) return str.size() - 1; @@ -111,6 +110,8 @@ return pos + 1; } + // Returns the position of the root directory in str. If there is no root + // directory in str, it returns StringRef::npos. size_t root_dir_start(StringRef str, Style style) { // case "c:/" if (real_style(style) == Style::windows) { @@ -118,10 +119,6 @@ return 2; } - // case "//" - if (str.size() == 2 && is_separator(str[0], style) && str[0] == str[1]) - return StringRef::npos; - // case "//net" if (str.size() > 3 && is_separator(str[0], style) && str[0] == str[1] && !is_separator(str[2], style)) { @@ -135,22 +132,29 @@ return StringRef::npos; } + // Returns the position past the end of the "parent path" of path. The parent + // path will not end in '/', unless the parent is the root directory. If the + // path has no parent, 0 is returned. size_t parent_path_end(StringRef path, Style style) { size_t end_pos = filename_pos(path, style); bool filename_was_sep = path.size() > 0 && is_separator(path[end_pos], style); - // Skip separators except for root dir. - size_t root_dir_pos = root_dir_start(path.substr(0, end_pos), style); - - while (end_pos > 0 && (end_pos - 1) != root_dir_pos && + // Skip separators until we reach root dir (or the start of the string). + size_t root_dir_pos = root_dir_start(path, style); + while (end_pos > 0 && + (root_dir_pos == StringRef::npos || end_pos > root_dir_pos) && is_separator(path[end_pos - 1], style)) --end_pos; - if (end_pos == 1 && root_dir_pos == 0 && filename_was_sep) - return StringRef::npos; + if (end_pos == root_dir_pos && !filename_was_sep) { + // We've reached the root dir and the input path was *not* ending in a + // sequence of slashes. Include the root dir in the parent path. + return root_dir_pos + 1; + } + // Otherwise, just include before the last slash. return end_pos; } } // end unnamed namespace @@ -282,8 +286,8 @@ ++Position; } - // Treat trailing '/' as a '.'. - if (Position == Path.size()) { + // Treat trailing '/' as a '.', unless it is the root dir. + if (Position == Path.size() && Component != "/") { --Position; Component = "."; return *this; @@ -322,23 +326,23 @@ } reverse_iterator &reverse_iterator::operator++() { - // If we're at the end and the previous char was a '/', return '.' unless - // we are the root path. size_t root_dir_pos = root_dir_start(Path, S); - if (Position == Path.size() && Path.size() > root_dir_pos + 1 && - is_separator(Path[Position - 1], S)) { - --Position; - Component = "."; - return *this; - } // Skip separators unless it's the root directory. size_t end_pos = Position; - while (end_pos > 0 && (end_pos - 1) != root_dir_pos && is_separator(Path[end_pos - 1], S)) --end_pos; + // Treat trailing '/' as a '.', unless it is the root dir. + if (Position == Path.size() && !Path.empty() && + is_separator(Path.back(), S) && + (root_dir_pos == StringRef::npos || end_pos - 1 > root_dir_pos)) { + --Position; + Component = "."; + return *this; + } + // Find next separator. size_t start_pos = filename_pos(Path.substr(0, end_pos), S); Component = Path.slice(start_pos, end_pos); Index: llvm/trunk/unittests/Support/Path.cpp =================================================================== --- llvm/trunk/unittests/Support/Path.cpp +++ llvm/trunk/unittests/Support/Path.cpp @@ -184,6 +184,12 @@ EXPECT_EQ("\\", path::filename("c:\\", path::Style::windows)); EXPECT_EQ("c:", path::parent_path("c:\\", path::Style::windows)); + EXPECT_EQ("/", path::filename("///")); + EXPECT_EQ("", path::parent_path("///")); + + EXPECT_EQ("\\", path::filename("c:\\\\", path::Style::windows)); + EXPECT_EQ("c:", path::parent_path("c:\\\\", path::Style::windows)); + EXPECT_EQ("bar", path::filename("/foo/bar")); EXPECT_EQ("/foo", path::parent_path("/foo/bar")); @@ -204,6 +210,19 @@ EXPECT_EQ("foo", path::filename("//net/foo")); EXPECT_EQ("//net/", path::parent_path("//net/foo")); + + // These checks are just to make sure we do something reasonable with the + // paths below. They are not meant to prescribe the one true interpretation of + // these paths. Other decompositions (e.g. "//" -> "" + "//") are also + // possible. + EXPECT_EQ("/", path::filename("//")); + EXPECT_EQ("", path::parent_path("//")); + + EXPECT_EQ("\\", path::filename("\\\\", path::Style::windows)); + EXPECT_EQ("", path::parent_path("\\\\", path::Style::windows)); + + EXPECT_EQ("\\", path::filename("\\\\\\", path::Style::windows)); + EXPECT_EQ("", path::parent_path("\\\\\\", path::Style::windows)); } static std::vector @@ -214,6 +233,8 @@ TEST(Support, PathIterator) { EXPECT_THAT(GetComponents("/foo"), testing::ElementsAre("/", "foo")); EXPECT_THAT(GetComponents("/"), testing::ElementsAre("/")); + EXPECT_THAT(GetComponents("//"), testing::ElementsAre("/")); + EXPECT_THAT(GetComponents("///"), testing::ElementsAre("/")); EXPECT_THAT(GetComponents("c/d/e/foo.txt"), testing::ElementsAre("c", "d", "e", "foo.txt")); EXPECT_THAT(GetComponents(".c/.d/../."), @@ -234,10 +255,8 @@ SmallVector, 4> Paths; Paths.emplace_back("/foo/", path::Style::native); Paths.emplace_back("/foo//", path::Style::native); - Paths.emplace_back("//net//", path::Style::native); Paths.emplace_back("//net/foo/", path::Style::native); Paths.emplace_back("c:\\foo\\", path::Style::windows); - Paths.emplace_back("c:\\\\", path::Style::windows); for (auto &Path : Paths) { SCOPED_TRACE(Path.first); @@ -249,6 +268,8 @@ RootPaths.emplace_back("/", path::Style::native); RootPaths.emplace_back("//net/", path::Style::native); RootPaths.emplace_back("c:\\", path::Style::windows); + RootPaths.emplace_back("//net//", path::Style::native); + RootPaths.emplace_back("c:\\\\", path::Style::windows); for (auto &Path : RootPaths) { SCOPED_TRACE(Path.first);