[libcxx-commits] [libcxx] [libc++][filesystem] Report an error for canonical("") (PR #215031)
Yordan Vásquez via libcxx-commits
libcxx-commits at lists.llvm.org
Sun Aug 9 13:14:35 PDT 2026
https://github.com/vyordan updated https://github.com/llvm/llvm-project/pull/215031
>From ea8882e9991d9f1037f9aa58114a4d20a9d67501 Mon Sep 17 00:00:00 2001
From: vyordan <vyordangiovani at gmail.com>
Date: Sat, 8 Aug 2026 16:50:22 -0600
Subject: [PATCH] [libc++][filesystem] Report an error for canonical("")
canonical(p) requires !exists(p) to be an error per [fs.op.canonical].
An empty path never refers to an existing file (exists(path()) is
always false, matching how a POSIX stat("") call fails with ENOENT),
so canonical("") must report an error instead of silently returning
the current directory.
The current implementation absolutizes p before checking whether it
resolves to something real: __do_absolute(p) computes cwd / p, and
appending an empty path via operator/= yields cwd itself (with a
trailing separator), which does exist. This hides the required error,
since no information survives about whether the original argument was
empty. This is a corner case unique to the empty path: no other
relative path can "disappear" when composed with the cwd.
Fix this at the source by checking orig_p.empty() in __canonical
before absolutizing, using the original, unmodified argument.
absolute() is unaffected by design: [fs.op.absolute] explicitly
discourages treating !exists(p) as an error, since it is a purely
lexical composition (current_path() / p). This matches the existing
absolute.pass.cpp expectation that absolute("") == current_path() / "".
weakly_canonical() is also unaffected in its observable behavior:
[fs.op.weakly_canonical] never requires existence either. It used
__canonical("", ec) internally as a shortcut relying on the old
(buggy) behavior to resolve to the current directory; both internal
call sites are updated to call __current_path() directly instead,
producing the exact same result without depending on the
now-corrected canonical(""). Since relative() and proximate() are
built solely on top of weakly_canonical(), they require no changes
either.
Fixes #77170
---
libcxx/docs/ReleaseNotes/24.rst | 3 ++
libcxx/src/filesystem/operations.cpp | 12 ++++++--
.../fs.op.canonical/canonical.pass.cpp | 30 +++++++++++++++++++
3 files changed, 43 insertions(+), 2 deletions(-)
diff --git a/libcxx/docs/ReleaseNotes/24.rst b/libcxx/docs/ReleaseNotes/24.rst
index e9a36ae94392f..5204861f374e0 100644
--- a/libcxx/docs/ReleaseNotes/24.rst
+++ b/libcxx/docs/ReleaseNotes/24.rst
@@ -67,6 +67,9 @@ Potentially breaking changes
- Unused objects of containers are now diagnosed. To ease the transition ``_LIBCPP_DISABLE_UNUSED_STRUCT_WARNINGS`` can
be defined as an escape hatch. This escape hatch will be removed in LLVM 24.
+- ``std::filesystem::canonical`` now reports an error when called with an empty path, per ``[fs.op.canonical]``.
+ (`Github <https://llvm.org/PR77170>`__)
+
Announcements About Future Releases
-----------------------------------
diff --git a/libcxx/src/filesystem/operations.cpp b/libcxx/src/filesystem/operations.cpp
index 3ff62c102d0ad..367b6c0ace12b 100644
--- a/libcxx/src/filesystem/operations.cpp
+++ b/libcxx/src/filesystem/operations.cpp
@@ -99,6 +99,12 @@ path __canonical(path const& orig_p, error_code* ec) {
path cwd;
ErrorHandler<path> err("canonical", ec, &orig_p, &cwd);
+ // canonical() requires !exists(p) to be an error ([fs.op.canonical]). An empty
+ // path never exists, but __do_absolute("") below would resolve to the (existing)
+ // current directory and hide that error, so it must be checked here first.
+ if (orig_p.empty())
+ return err.report(errc::no_such_file_or_directory);
+
path p = __do_absolute(orig_p, &cwd, ec);
#if (defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112) || defined(_WIN32)
std::unique_ptr<path::value_type, decltype(&::free)> hold(detail::realpath(p.c_str(), nullptr), &::free);
@@ -1042,8 +1048,9 @@ path __temp_directory_path(error_code* ec) {
path __weakly_canonical(const path& p, error_code* ec) {
ErrorHandler<path> err("weakly_canonical", ec, &p);
+ // weakly_canonical() never requires its argument to exist ([fs.op.weakly.canonical]).
if (p.empty())
- return __canonical("", ec);
+ return __current_path(ec);
path result;
path tmp;
@@ -1069,7 +1076,8 @@ path __weakly_canonical(const path& p, error_code* ec) {
--PP;
}
if (PP.State_ == PathParser::PS_BeforeBegin) {
- result = __canonical("", &m_ec);
+ // No element of p exists; canonical() of that degenerate case is the current directory.
+ result = __current_path(&m_ec);
if (m_ec) {
return err.report(m_ec);
}
diff --git a/libcxx/test/std/input.output/filesystems/fs.op.funcs/fs.op.canonical/canonical.pass.cpp b/libcxx/test/std/input.output/filesystems/fs.op.funcs/fs.op.canonical/canonical.pass.cpp
index 5323f3bc8337f..081aabb24ea93 100644
--- a/libcxx/test/std/input.output/filesystems/fs.op.funcs/fs.op.canonical/canonical.pass.cpp
+++ b/libcxx/test/std/input.output/filesystems/fs.op.funcs/fs.op.canonical/canonical.pass.cpp
@@ -92,6 +92,35 @@ static void test_dne_path()
}
}
+// https://llvm.org/PR77170
+// Per [fs.op.canonical], "!exists(p) is an error", and an empty path never
+// refers to an existing file (exists(path()) is always false). canonical("")
+// must therefore report an error instead of silently returning the current
+// directory.
+static void test_empty_path() {
+ std::error_code ec = GetTestEC();
+ {
+ const path ret = canonical(path{}, ec);
+ assert(ec != GetTestEC());
+ assert(ec);
+ assert(ret == path{});
+ }
+ {
+ TEST_THROWS_TYPE(filesystem_error, canonical(path{}));
+ }
+
+ ec = GetTestEC();
+ {
+ const path ret = canonical("", ec);
+ assert(ec != GetTestEC());
+ assert(ec);
+ assert(ret == path{});
+ }
+ {
+ TEST_THROWS_TYPE(filesystem_error, canonical(""));
+ }
+}
+
static void test_exception_contains_paths()
{
#ifndef TEST_HAS_NO_EXCEPTIONS
@@ -121,6 +150,7 @@ int main(int, char**) {
signature_test();
test_canonical();
test_dne_path();
+ test_empty_path();
test_exception_contains_paths();
return 0;
More information about the libcxx-commits
mailing list