Skip to content

Commit aa427b1

Browse files
authored
[libc++] Fix backslash as root dir breaks lexically_relative, lexically_proximate and hash_value on Windows (#99780)
Various functions like hash_value, lexically_proximate and lexically_relative would incorrectly handle backslashes in the root directory on Windows, causing behavior that is inconsistent with the equality comparison for a path.
1 parent abdc2b1 commit aa427b1

File tree

3 files changed

+39
-28
lines changed

3 files changed

+39
-28
lines changed

libcxx/src/filesystem/path.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ path path::lexically_relative(const path& base) const {
267267
// Find the first mismatching element
268268
auto PP = PathParser::CreateBegin(__pn_);
269269
auto PPBase = PathParser::CreateBegin(base.__pn_);
270-
while (PP && PPBase && PP.State_ == PPBase.State_ && *PP == *PPBase) {
270+
while (PP && PPBase && PP.State_ == PPBase.State_ && (*PP == *PPBase || PP.inRootDir())) {
271271
++PP;
272272
++PPBase;
273273
}
@@ -368,7 +368,8 @@ size_t hash_value(const path& __p) noexcept {
368368
size_t hash_value = 0;
369369
hash<string_view_t> hasher;
370370
while (PP) {
371-
hash_value = __hash_combine(hash_value, hasher(*PP));
371+
string_view_t Part = PP.inRootDir() ? PATHSTR("/") : *PP;
372+
hash_value = __hash_combine(hash_value, hasher(Part));
372373
++PP;
373374
}
374375
return hash_value;

libcxx/test/std/input.output/filesystems/class.path/path.member/path.compare.pass.cpp

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -47,13 +47,20 @@ struct PathCompareTest {
4747
int expect;
4848
};
4949

50-
#define LONGA "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
51-
#define LONGB "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
52-
#define LONGC "CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
53-
#define LONGD "DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
54-
const PathCompareTest CompareTestCases[] =
55-
{
56-
{"", "", 0},
50+
#define LONGA \
51+
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" \
52+
"AAAAAAAA"
53+
#define LONGB \
54+
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" \
55+
"BBBBBBBB"
56+
#define LONGC \
57+
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC" \
58+
"CCCCCCCC"
59+
#define LONGD \
60+
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD" \
61+
"DDDDDDDD"
62+
const PathCompareTest CompareTestCases[] = {
63+
{"", "", 0},
5764
{"a", "", 1},
5865
{"", "a", -1},
5966
{"a/b/c", "a/b/c", 0},
@@ -62,14 +69,19 @@ const PathCompareTest CompareTestCases[] =
6269
{"a/b", "a/b/c", -1},
6370
{"a/b/c", "a/b", 1},
6471
{"a/b/", "a/b/.", -1},
65-
{"a/b/", "a/b", 1},
72+
{"a/b/", "a/b", 1},
6673
{"a/b//////", "a/b/////.", -1},
6774
{"a/.././b", "a///..//.////b", 0},
6875
{"//foo//bar///baz////", "//foo/bar/baz/", 0}, // duplicate separators
69-
{"///foo/bar", "/foo/bar", 0}, // "///" is not a root directory
70-
{"/foo/bar/", "/foo/bar", 1}, // trailing separator
76+
{"///foo/bar", "/foo/bar", 0}, // "///" is not a root directory
77+
{"/foo/bar/", "/foo/bar", 1}, // trailing separator
7178
{"foo", "/foo", -1}, // if !this->has_root_directory() and p.has_root_directory(), a value less than 0.
72-
{"/foo", "foo", 1}, // if this->has_root_directory() and !p.has_root_directory(), a value greater than 0.
79+
{"/foo", "foo", 1}, // if this->has_root_directory() and !p.has_root_directory(), a value greater than 0.
80+
#ifdef _WIN32
81+
{"C:/a", "C:\\a", 0},
82+
#else
83+
{"C:/a", "C:\\a", -1},
84+
#endif
7385
{("//" LONGA "////" LONGB "/" LONGC "///" LONGD), ("//" LONGA "/" LONGB "/" LONGC "/" LONGD), 0},
7486
{(LONGA "/" LONGB "/" LONGC), (LONGA "/" LONGB "/" LONGB), 1}
7587

@@ -79,23 +91,19 @@ const PathCompareTest CompareTestCases[] =
7991
#undef LONGC
8092
#undef LONGD
8193

82-
static inline int normalize_ret(int ret)
83-
{
84-
return ret < 0 ? -1 : (ret > 0 ? 1 : 0);
85-
}
94+
static inline int normalize_ret(int ret) { return ret < 0 ? -1 : (ret > 0 ? 1 : 0); }
8695

87-
void test_compare_basic()
88-
{
96+
void test_compare_basic() {
8997
using namespace fs;
90-
for (auto const & TC : CompareTestCases) {
98+
for (auto const& TC : CompareTestCases) {
9199
const path p1(TC.LHS);
92100
const path p2(TC.RHS);
93101
std::string RHS(TC.RHS);
94102
const path::string_type R(RHS.begin(), RHS.end());
95103
const std::basic_string_view<path::value_type> RV(R);
96-
const path::value_type *Ptr = R.c_str();
97-
const int E = TC.expect;
98-
{ // compare(...) functions
104+
const path::value_type* Ptr = R.c_str();
105+
const int E = TC.expect;
106+
{ // compare(...) functions
99107
DisableAllocationGuard g; // none of these operations should allocate
100108

101109
// check runtime results
@@ -113,7 +121,7 @@ void test_compare_basic()
113121
// check signatures
114122
ASSERT_NOEXCEPT(p1.compare(p2));
115123
}
116-
{ // comparison operators
124+
{ // comparison operators
117125
DisableAllocationGuard g; // none of these operations should allocate
118126

119127
// check signatures
@@ -180,14 +188,14 @@ void test_compare_elements() {
180188

181189
auto BuildPath = [](std::vector<std::string> const& Elems) {
182190
fs::path p;
183-
for (auto &E : Elems)
191+
for (auto& E : Elems)
184192
p /= E;
185193
return p;
186194
};
187195

188-
for (auto &TC : TestCases) {
189-
fs::path LHS = BuildPath(TC.LHSElements);
190-
fs::path RHS = BuildPath(TC.RHSElements);
196+
for (auto& TC : TestCases) {
197+
fs::path LHS = BuildPath(TC.LHSElements);
198+
fs::path RHS = BuildPath(TC.RHSElements);
191199
const int ExpectCmp = CompareElements(TC.LHSElements, TC.RHSElements);
192200
assert(ExpectCmp == TC.Expect);
193201
const int GotCmp = normalize_ret(LHS.compare(RHS));

libcxx/test/std/input.output/filesystems/class.path/path.member/path.gen/lexically_relative_and_proximate.pass.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,11 @@ int main(int, char**) {
4141
#ifdef _WIN32
4242
{"//net/", "//net", ""},
4343
{"//net", "//net/", ""},
44+
{"C:\\a\\b", "C:/a", "b"},
4445
#else
4546
{"//net/", "//net", "."},
4647
{"//net", "//net/", "."},
48+
{"C:\\a\\b", "C:/a", "../../C:\\a\\b"},
4749
#endif
4850
{"//base", "a", ""},
4951
{"a", "a", "."},

0 commit comments

Comments
 (0)