[libcxx-commits] [libcxx] [libc++] Do not remove a root-name followed by ".." in `path::lexically_normal()` (PR #201261)
via libcxx-commits
libcxx-commits at lists.llvm.org
Tue Jun 2 21:08:40 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libcxx
Author: Igor Kudrin (igorkudrin)
<details>
<summary>Changes</summary>
For Windows paths like `"C:.."`, `path::lexically_normal()` should preserve both the root-name and the `".."` component. In [fs.path.generic]p6, clause (5) prescribes removing `".."` only after non-dot-dot filenames. Since a root-name is not a filename, this clause does not apply. Clause (6) only applies when there is a root-directory, which is absent in this case. Therefore, the root-name and these `".."` components should not be removed.
This change aligns `libc++` with MSVC.
Example code:
```
#include <filesystem>
#include <iostream>
int main()
{
static const char *samples[] = {
"C:..",
"C:../..",
"C:foo\\..\\..",
0
};
for (int i = 0; samples[i]; ++i) {
auto norm = std::filesystem::path(samples[i]).lexically_normal();
std::cout << "'" << samples[i] << "' ==> '" << norm.string() << "'\n";
}
}
```
Output comparison:
```
> test.msvc.exe
'C:..' ==> 'C:..'
'C:../..' ==> 'C:..\..'
'C:foo\..\..' ==> 'C:..'
> test.clang.exe (before fix)
'C:..' ==> '.'
'C:../..' ==> '..'
'C:foo\..\..' ==> '.'
```
---
Full diff: https://github.com/llvm/llvm-project/pull/201261.diff
2 Files Affected:
- (modified) libcxx/src/filesystem/path.cpp (+10-11)
- (modified) libcxx/test/std/input.output/filesystems/class.path/path.member/path.gen/lexically_normal.pass.cpp (+6)
``````````diff
diff --git a/libcxx/src/filesystem/path.cpp b/libcxx/src/filesystem/path.cpp
index 12a698da901a4..7b60e00625a36 100644
--- a/libcxx/src/filesystem/path.cpp
+++ b/libcxx/src/filesystem/path.cpp
@@ -140,21 +140,20 @@ string_view_t path::__extension() const { return parser::separate_filename(__fil
////////////////////////////////////////////////////////////////////////////
// path.gen
-enum PathPartKind : unsigned char { PK_None, PK_RootSep, PK_Filename, PK_Dot, PK_DotDot, PK_TrailingSep };
+enum PathPartKind : unsigned char { PK_None, PK_RootName, PK_RootSep, PK_Filename, PK_Dot, PK_DotDot, PK_TrailingSep };
-static PathPartKind ClassifyPathPart(string_view_t Part) {
+static PathPartKind ClassifyPathPart(const PathParser &PP) {
+ if (PP.inRootName())
+ return PK_RootName;
+ if (PP.inRootDir())
+ return PK_RootSep;
+ string_view_t Part = *PP;
if (Part.empty())
return PK_TrailingSep;
if (Part == PATHSTR("."))
return PK_Dot;
if (Part == PATHSTR(".."))
return PK_DotDot;
- if (Part == PATHSTR("/"))
- return PK_RootSep;
-#if defined(_LIBCPP_WIN32API)
- if (Part == PATHSTR("\\"))
- return PK_RootSep;
-#endif
return PK_Filename;
}
@@ -184,13 +183,13 @@ path path::lexically_normal() const {
// Build a stack containing the remaining elements of the path, popping off
// elements which occur before a '..' entry.
for (auto PP = PathParser::CreateBegin(__pn_); PP; ++PP) {
- auto Part = *PP;
- PathPartKind Kind = ClassifyPathPart(Part);
+ PathPartKind Kind = ClassifyPathPart(PP);
switch (Kind) {
case PK_Filename:
+ case PK_RootName:
case PK_RootSep: {
// Add all non-dot and non-dot-dot elements to the stack of elements.
- AddPart(Kind, Part);
+ AddPart(Kind, *PP);
MaybeNeedTrailingSep = false;
break;
}
diff --git a/libcxx/test/std/input.output/filesystems/class.path/path.member/path.gen/lexically_normal.pass.cpp b/libcxx/test/std/input.output/filesystems/class.path/path.member/path.gen/lexically_normal.pass.cpp
index e90f67bb80931..5dfb24d52471e 100644
--- a/libcxx/test/std/input.output/filesystems/class.path/path.member/path.gen/lexically_normal.pass.cpp
+++ b/libcxx/test/std/input.output/filesystems/class.path/path.member/path.gen/lexically_normal.pass.cpp
@@ -107,6 +107,12 @@ int main(int, char**) {
{"foo/bar/baz/../../", "foo/"},
{"foo/bar/./..", "foo/"},
{"foo/bar/./../", "foo/"},
+#ifdef _WIN32
+ /// A root-name followed by a dot-dot filename should not be removed.
+ {"C:..", "C:.."},
+ {"C:..\\..", "C:..\\.."},
+ {"C:foo\\..\\..", "C:.."},
+#endif
// p6: If there is a root-directory, remove all dot-dot filenames and any
// directory-separators immediately following them. [ Note: These dot-dot
// filenames attempt to refer to nonexistent parent directories. - end note ]
``````````
</details>
https://github.com/llvm/llvm-project/pull/201261
More information about the libcxx-commits
mailing list