[llvm] [Windows][Support] Add helper to expand short 8.3 form paths (PR #178480)

Ben Dunbobbin via llvm-commits llvm-commits at lists.llvm.org
Tue Feb 10 04:24:46 PST 2026


https://github.com/bd1976bris updated https://github.com/llvm/llvm-project/pull/178480

>From 836e04c06ebfebb8fa68bc6de26e6835472d60e9 Mon Sep 17 00:00:00 2001
From: Ben <ben.dunbobbin at sony.com>
Date: Wed, 28 Jan 2026 18:14:24 +0000
Subject: [PATCH 1/7] [Windows][Support] Add helper to expand short 8.3 form
 paths

Windows supports short 8.3 form filenames (for example,
compile_commands.json -> COMPIL~1.JSO) for legacy reasons. See:
https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file#short-vs-long-names

Such paths are not unusual because, on Windows, the system temporary
directory is commonly derived from the TMP/TEMP environment variables.
For historical compatibility reasons, these variables are often set to
short 8.3 form paths on systems where user names exceed eight
characters.

Introduce windows::makeLongFormPath() to convert UTF-8 paths to their
long form by expanding any 8.3 components via GetLongPathNameW.

As part of this change, extended-length path prefix handling is
centralized by adding stripExtendedPrefix() and reusing it in
realPathFromHandle(), and widenPath() is cleaned up to use shared
prefix constants.
---
 .../llvm/Support/Windows/WindowsSupport.h     |   5 +
 llvm/lib/Support/Windows/Path.inc             |  92 ++++++++++--
 llvm/unittests/Support/Path.cpp               | 133 ++++++++++++++++++
 3 files changed, 215 insertions(+), 15 deletions(-)

diff --git a/llvm/include/llvm/Support/Windows/WindowsSupport.h b/llvm/include/llvm/Support/Windows/WindowsSupport.h
index f35e7b55cb8d3..50a2540dba687 100644
--- a/llvm/include/llvm/Support/Windows/WindowsSupport.h
+++ b/llvm/include/llvm/Support/Windows/WindowsSupport.h
@@ -249,6 +249,11 @@ LLVM_ABI std::error_code widenPath(const Twine &Path8,
 /// ensuring we're not retrieving a malicious injected module but a module
 /// loaded from the system path.
 LLVM_ABI HMODULE loadSystemModuleSecure(LPCWSTR lpModuleName);
+
+/// Convert a UTF-8 path to a long form UTF-8 path expanding any short 8.3 form
+/// components.
+LLVM_ABI std::error_code makeLongFormPath(const Twine &Path8,
+                                          llvm::SmallVectorImpl<char> &Result8);
 } // end namespace windows
 } // end namespace sys
 } // end namespace llvm.
diff --git a/llvm/lib/Support/Windows/Path.inc b/llvm/lib/Support/Windows/Path.inc
index c03b85b2f4bb3..f58b54c3afb0c 100644
--- a/llvm/lib/Support/Windows/Path.inc
+++ b/llvm/lib/Support/Windows/Path.inc
@@ -60,6 +60,35 @@ static bool is_separator(const wchar_t value) {
   }
 }
 
+// Extended-length path prefix constants (UTF-8).
+static constexpr llvm::StringLiteral ExtendedPrefix8 = R"(\\?\)";
+static constexpr llvm::StringLiteral ExtendedUNCPrefix8 = R"(\\?\UNC\)";
+
+// Extended-length path prefix constants (UTF-16).
+static constexpr wchar_t ExtendedPrefix16[] = LR"(\\?\)";
+static constexpr wchar_t ExtendedUNCPathPrefix16[] = LR"(\\?\UNC\)";
+
+static constexpr DWORD ExtendedPrefix16Len =
+    static_cast<DWORD>(std::size(ExtendedPrefix16) - 1);
+static constexpr DWORD ExtendedUNCPathPrefix16Len =
+    static_cast<DWORD>(std::size(ExtendedUNCPathPrefix16) - 1);
+
+static void stripExtendedPrefix(wchar_t *&Data, DWORD &CountChars) {
+  if (CountChars >= ExtendedUNCPathPrefix16Len &&
+      ::wmemcmp(Data, ExtendedUNCPathPrefix16, ExtendedUNCPathPrefix16Len) ==
+          0) {
+    // Convert \\?\UNC\foo\bar to \\foo\bar
+    CountChars -= 6;
+    Data += 6;
+    Data[0] = L'\\';
+  } else if (CountChars >= ExtendedPrefix16Len &&
+             ::wmemcmp(Data, ExtendedPrefix16, ExtendedPrefix16Len) == 0) {
+    // Convert \\?\C:\foo to C:\foo
+    CountChars -= 4;
+    Data += 4;
+  }
+}
+
 namespace llvm {
 namespace sys {
 namespace windows {
@@ -95,10 +124,8 @@ std::error_code widenPath(const Twine &Path8, SmallVectorImpl<wchar_t> &Path16,
       return mapWindowsError(::GetLastError());
   }
 
-  const char *const LongPathPrefix = "\\\\?\\";
-
   if ((Path16.size() + CurPathLen) < MaxPathLen ||
-      Path8Str.starts_with(LongPathPrefix))
+      Path8Str.starts_with(ExtendedPrefix8))
     return std::error_code();
 
   if (!IsAbsolute) {
@@ -116,17 +143,61 @@ std::error_code widenPath(const Twine &Path8, SmallVectorImpl<wchar_t> &Path16,
   assert(!RootName.empty() &&
          "Root name cannot be empty for an absolute path!");
 
-  SmallString<2 * MAX_PATH> FullPath(LongPathPrefix);
+  SmallString<2 * MAX_PATH> FullPath;
   if (RootName[1] != ':') { // Check if UNC.
-    FullPath.append("UNC\\");
+    FullPath.append(ExtendedUNCPrefix8);
     FullPath.append(Path8Str.begin() + 2, Path8Str.end());
   } else {
+    FullPath.append(ExtendedPrefix8);
     FullPath.append(Path8Str);
   }
 
   return UTF8ToUTF16(FullPath, Path16);
 }
 
+std::error_code makeLongFormPath(const Twine &Path8,
+                                 llvm::SmallVectorImpl<char> &Result8) {
+  SmallString<128> PathStorage;
+  StringRef PathStr = Path8.toStringRef(PathStorage);
+  bool HadPrefix = PathStr.starts_with(ExtendedPrefix8);
+
+  SmallVector<wchar_t, 128> Path16;
+  if (std::error_code EC = widenPath(PathStr, Path16))
+    return EC;
+
+  // Start with a buffer equal to input.
+  llvm::SmallVector<wchar_t, 128> Long16;
+  DWORD Len = static_cast<DWORD>(Path16.size());
+
+  do {
+    Long16.resize_for_overwrite(Len);
+
+    Len = ::GetLongPathNameW(Path16.data(), Long16.data(), Len);
+
+    // A zero return value indicates a failure other than insufficient space.
+    if (Len == 0)
+      return mapWindowsError(::GetLastError());
+
+    // If there's insufficient space, the return value is the required size in
+    // characters *including* the null terminator, and therefore greater than
+    // the buffer size we provided. Equality would imply success with no room
+    // for the terminator and should not occur for this API.
+    assert(Len != Long16.size());
+  } while (Len > Long16.size());
+
+  // On success, GetLongPathNameW returns the number of characters not
+  // including the null-terminator.
+  Long16.truncate(Len);
+
+  // Strip \\?\ or \\?\UNC\ extended length prefix if it wasn't part of the
+  // original path.
+  wchar_t *Data = Long16.data();
+  if (!HadPrefix)
+    stripExtendedPrefix(Data, Len);
+
+  return sys::windows::UTF16ToUTF8(Data, Len, Result8);
+}
+
 } // end namespace windows
 
 namespace fs {
@@ -407,16 +478,7 @@ static std::error_code realPathFromHandle(HANDLE H,
   // paths don't get canonicalized by file APIs.
   wchar_t *Data = Buffer.data();
   DWORD CountChars = Buffer.size();
-  if (CountChars >= 8 && ::memcmp(Data, L"\\\\?\\UNC\\", 16) == 0) {
-    // Convert \\?\UNC\foo\bar to \\foo\bar
-    CountChars -= 6;
-    Data += 6;
-    Data[0] = '\\';
-  } else if (CountChars >= 4 && ::memcmp(Data, L"\\\\?\\", 8) == 0) {
-    // Convert \\?\c:\foo to c:\foo
-    CountChars -= 4;
-    Data += 4;
-  }
+  stripExtendedPrefix(Data, CountChars);
 
   // Convert the result from UTF-16 to UTF-8.
   if (std::error_code EC = UTF16ToUTF8(Data, CountChars, RealPath))
diff --git a/llvm/unittests/Support/Path.cpp b/llvm/unittests/Support/Path.cpp
index 7f22b7c2edb4b..cef4d66ce8b26 100644
--- a/llvm/unittests/Support/Path.cpp
+++ b/llvm/unittests/Support/Path.cpp
@@ -32,6 +32,7 @@
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/Support/Chrono.h"
 #include "llvm/Support/Windows/WindowsSupport.h"
+#include <fileapi.h>
 #include <windows.h>
 #include <winerror.h>
 #endif
@@ -2485,6 +2486,138 @@ TEST_F(FileSystemTest, widenPath) {
 #endif
 
 #ifdef _WIN32
+/// Checks whether short 8.3 form names are enabled in the given UTF-8 path.
+/// This is the best way I have found of checking this. Note that short 8.3 form
+/// names can be enabled and disabled on a per-folder, per-volume, and
+/// per-system basis.
+static bool areShortNamesEnabled(llvm::StringRef Path8) {
+  // Create a directory under Path8 with a name long enough that Windows will
+  // provide a short 8.3 form name, if short 8.3 form names are enabled.
+  SmallString<256> Dir(Path8);
+  path::append(Dir, "verylongdir");
+  if (std::error_code EC = fs::create_directories(Dir))
+    return false;
+
+  // Check if the expected short 8.3 form name was created.
+  SmallString<256> Short(Path8);
+  path::append(Short, "verylo~1");
+  bool Result = fs::is_directory(Short);
+
+  fs::remove_directories(Dir);
+  return Result;
+}
+
+/// Returns the short 8.3 form path for the given UTF-8 path, or an empty string
+/// on failure. Uses Win32 GetShortPathNameW.
+static std::string getShortPathName(llvm::StringRef Path8) {
+  // Convert UTF-8 to UTF-16.
+  SmallVector<wchar_t, MAX_PATH> Path16;
+  if (std::error_code EC = sys::windows::widenPath(Path8, Path16))
+    return {};
+
+  // Get the required buffer size for the short 8.3 form path (includes null
+  // terminator).
+  DWORD Required = ::GetShortPathNameW(Path16.data(), nullptr, 0);
+  if (Required == 0)
+    return {};
+
+  SmallVector<wchar_t, MAX_PATH> ShortPath;
+  ShortPath.resize_for_overwrite(Required);
+
+  DWORD Written =
+      ::GetShortPathNameW(Path16.data(), ShortPath.data(), Required);
+  if (Written == 0 || Written >= Required)
+    return {};
+
+  ShortPath.truncate(Written);
+
+  SmallString<128> Utf8Result;
+  if (std::error_code EC = sys::windows::UTF16ToUTF8(
+          ShortPath.data(), ShortPath.size(), Utf8Result))
+    return {};
+
+  return std::string(Utf8Result);
+}
+
+/// Returns true if the two paths refer to the same file or directory by
+/// comparing their UniqueIDs.
+static bool sameEntity(llvm::StringRef P1, llvm::StringRef P2) {
+  fs::UniqueID ID1, ID2;
+  return !fs::getUniqueID(P1, ID1) && !fs::getUniqueID(P2, ID2) && ID1 == ID2;
+}
+
+/// Removes the Windows extended path prefix (\\?\ or \\?\UNC\) from the given
+/// UTF-8 path, if present.
+static std::string stripPrefix(llvm::StringRef P) {
+  if (P.starts_with(R"(\\?\UNC\)"))
+    return "\\" + P.drop_front(7).str();
+  if (P.starts_with(R"(\\?\)"))
+    return P.drop_front(4).str();
+  return P.str();
+}
+
+TEST_F(FileSystemTest, makeLongFormPath) {
+  if (!areShortNamesEnabled(TestDirectory.str()))
+    GTEST_SKIP() << "Short 8.3 form names not enabled in: " << TestDirectory;
+
+  // Setup: A test directory longer than 8 characters for which a distinct
+  // short 8.3 form name will be created on Windows. Typically, 123456~1.
+  constexpr const char *OneDir = "\\123456789"; // >8 chars
+
+  // Setup: Create a path where even if all components were reduced to short 8.3
+  // form names, the total length would exceed MAX_PATH.
+  SmallString<MAX_PATH * 2> Deep(TestDirectory);
+  const size_t NLevels = (MAX_PATH / 8) + 1;
+  for (size_t I = 0; I < NLevels; ++I)
+    Deep.append(OneDir);
+
+  ASSERT_NO_ERROR(fs::create_directories(Deep));
+
+  // Setup: Create prefixed and non-prefixed short 8.3 form paths from the deep
+  // test path we just created.
+  std::string DeepShortWithPrefix = getShortPathName(Deep);
+  ASSERT_TRUE(StringRef(DeepShortWithPrefix).starts_with(R"(\\?\)"))
+      << "Expected prefixed short 8.3 form path, got: " << DeepShortWithPrefix;
+  std::string DeepShort = stripPrefix(DeepShortWithPrefix);
+
+  // Setup: Create a short 8.3 form path for the first-level directory.
+  SmallString<256> FirstLevel(TestDirectory);
+  FirstLevel.append(OneDir);
+  std::string Short = getShortPathName(FirstLevel);
+  ASSERT_FALSE(Short.empty())
+      << "Expected short 8.3 form path for test directory.";
+
+  // Case 1: Non-existent short 8.3 form path.
+  SmallString<128> NoExist("NotEre~1");
+  ASSERT_FALSE(fs::exists(NoExist));
+  SmallString<128> NoExistResult;
+  EXPECT_TRUE(windows::makeLongFormPath(NoExist, NoExistResult));
+  EXPECT_TRUE(NoExistResult.empty());
+
+  // Case 2: Valid short 8.3 form path.
+  SmallString<128> ShortResult;
+  ASSERT_FALSE(windows::makeLongFormPath(Short, ShortResult));
+  EXPECT_TRUE(sameEntity(Short, ShortResult));
+
+  // Case 3: Deep short 8.3 form path without \\?\ prefix.
+  SmallString<128> DeepResult;
+  ASSERT_FALSE(windows::makeLongFormPath(DeepShort, DeepResult));
+  EXPECT_TRUE(sameEntity(DeepShort, DeepResult));
+  EXPECT_FALSE(StringRef(DeepResult).starts_with(R"(\\?\)"))
+      << "Expected unprefixed result, got: " << DeepResult;
+
+  // Case 4: Deep short 8.3 form path with \\?\ prefix.
+  SmallString<128> DeepPrefixedResult;
+  ASSERT_FALSE(
+      windows::makeLongFormPath(DeepShortWithPrefix, DeepPrefixedResult));
+  EXPECT_TRUE(sameEntity(DeepShortWithPrefix, DeepPrefixedResult));
+  EXPECT_TRUE(StringRef(DeepPrefixedResult).starts_with(R"(\\?\)"))
+      << "Expected prefixed result, got: " << DeepPrefixedResult;
+
+  // Cleanup.
+  ASSERT_NO_ERROR(fs::remove_directories(TestDirectory.str()));
+}
+
 // Windows refuses lock request if file region is already locked by the same
 // process. POSIX system in this case updates the existing lock.
 TEST_F(FileSystemTest, FileLocker) {

>From f9c559c35efb99749f395ad2aab628b7d332529a Mon Sep 17 00:00:00 2001
From: Ben <ben.dunbobbin at sony.com>
Date: Mon, 2 Feb 2026 15:39:43 +0000
Subject: [PATCH 2/7] Do not assume the 8.3 name for "verylongdir"

---
 llvm/unittests/Support/Path.cpp | 31 +++++++++++++++++++------------
 1 file changed, 19 insertions(+), 12 deletions(-)

diff --git a/llvm/unittests/Support/Path.cpp b/llvm/unittests/Support/Path.cpp
index cef4d66ce8b26..4408679737297 100644
--- a/llvm/unittests/Support/Path.cpp
+++ b/llvm/unittests/Support/Path.cpp
@@ -2487,24 +2487,28 @@ TEST_F(FileSystemTest, widenPath) {
 
 #ifdef _WIN32
 /// Checks whether short 8.3 form names are enabled in the given UTF-8 path.
-/// This is the best way I have found of checking this. Note that short 8.3 form
-/// names can be enabled and disabled on a per-folder, per-volume, and
-/// per-system basis.
-static bool areShortNamesEnabled(llvm::StringRef Path8) {
+static llvm::Expected<bool> areShortNamesEnabled(llvm::StringRef Path8) {
   // Create a directory under Path8 with a name long enough that Windows will
   // provide a short 8.3 form name, if short 8.3 form names are enabled.
   SmallString<256> Dir(Path8);
   path::append(Dir, "verylongdir");
   if (std::error_code EC = fs::create_directories(Dir))
-    return false;
+    return llvm::errorCodeToError(EC);
+  scope_exit Close([&] { fs::remove_directories(Dir); });
 
-  // Check if the expected short 8.3 form name was created.
-  SmallString<256> Short(Path8);
-  path::append(Short, "verylo~1");
-  bool Result = fs::is_directory(Short);
+  SmallVector<wchar_t, MAX_PATH> Path16;
+  if (std::error_code EC = sys::windows::widenPath(Dir, Path16))
+    return llvm::errorCodeToError(EC);
+
+  WIN32_FIND_DATAW Data;
+  HANDLE H = ::FindFirstFileW(Path16.data(), &Data);
+  if (H == INVALID_HANDLE_VALUE)
+    return llvm::make_error<llvm::StringError>(
+        "FindFirstFileW returned invalid handle",
+        llvm::inconvertibleErrorCode());
+  ::FindClose(H);
 
-  fs::remove_directories(Dir);
-  return Result;
+  return (Data.cAlternateFileName[0] != L'\0');
 }
 
 /// Returns the short 8.3 form path for the given UTF-8 path, or an empty string
@@ -2557,7 +2561,10 @@ static std::string stripPrefix(llvm::StringRef P) {
 }
 
 TEST_F(FileSystemTest, makeLongFormPath) {
-  if (!areShortNamesEnabled(TestDirectory.str()))
+  auto Enabled = areShortNamesEnabled(TestDirectory.str());
+  ASSERT_TRUE(static_cast<bool>(Enabled))
+      << llvm::toString(Enabled.takeError());
+  if (!*Enabled)
     GTEST_SKIP() << "Short 8.3 form names not enabled in: " << TestDirectory;
 
   // Setup: A test directory longer than 8 characters for which a distinct

>From 43c32d5da064727667398941262341e82609cc48 Mon Sep 17 00:00:00 2001
From: Ben <ben.dunbobbin at sony.com>
Date: Mon, 2 Feb 2026 22:19:28 +0000
Subject: [PATCH 3/7] Use long path terminology.

Additionally:
- Add a '.' and '..' test case.
- Use MAX_PATH consistently for SmallStrings in the new test code.
---
 llvm/lib/Support/Windows/Path.inc | 51 +++++++++++++++----------------
 llvm/unittests/Support/Path.cpp   | 42 ++++++++++++++++++-------
 2 files changed, 56 insertions(+), 37 deletions(-)

diff --git a/llvm/lib/Support/Windows/Path.inc b/llvm/lib/Support/Windows/Path.inc
index f58b54c3afb0c..d1dd054fdd829 100644
--- a/llvm/lib/Support/Windows/Path.inc
+++ b/llvm/lib/Support/Windows/Path.inc
@@ -60,29 +60,28 @@ static bool is_separator(const wchar_t value) {
   }
 }
 
-// Extended-length path prefix constants (UTF-8).
-static constexpr llvm::StringLiteral ExtendedPrefix8 = R"(\\?\)";
-static constexpr llvm::StringLiteral ExtendedUNCPrefix8 = R"(\\?\UNC\)";
-
-// Extended-length path prefix constants (UTF-16).
-static constexpr wchar_t ExtendedPrefix16[] = LR"(\\?\)";
-static constexpr wchar_t ExtendedUNCPathPrefix16[] = LR"(\\?\UNC\)";
-
-static constexpr DWORD ExtendedPrefix16Len =
-    static_cast<DWORD>(std::size(ExtendedPrefix16) - 1);
-static constexpr DWORD ExtendedUNCPathPrefix16Len =
-    static_cast<DWORD>(std::size(ExtendedUNCPathPrefix16) - 1);
-
-static void stripExtendedPrefix(wchar_t *&Data, DWORD &CountChars) {
-  if (CountChars >= ExtendedUNCPathPrefix16Len &&
-      ::wmemcmp(Data, ExtendedUNCPathPrefix16, ExtendedUNCPathPrefix16Len) ==
-          0) {
+// Long path path prefix constants (UTF-8).
+static constexpr llvm::StringLiteral LongPathPrefix8 = R"(\\?\)";
+static constexpr llvm::StringLiteral LongPathUNCPrefix8 = R"(\\?\UNC\)";
+
+// Long path prefix constants (UTF-16).
+static constexpr wchar_t LongPathPrefix16[] = LR"(\\?\)";
+static constexpr wchar_t LongPathUNCPrefix16[] = LR"(\\?\UNC\)";
+
+static constexpr DWORD LongPathPrefix16Len =
+    static_cast<DWORD>(std::size(LongPathPrefix16) - 1);
+static constexpr DWORD LongPathUNCPrefix16Len =
+    static_cast<DWORD>(std::size(LongPathUNCPrefix16) - 1);
+
+static void stripLongPathPrefix(wchar_t *&Data, DWORD &CountChars) {
+  if (CountChars >= LongPathUNCPrefix16Len &&
+      ::wmemcmp(Data, LongPathUNCPrefix16, LongPathUNCPrefix16Len) == 0) {
     // Convert \\?\UNC\foo\bar to \\foo\bar
     CountChars -= 6;
     Data += 6;
     Data[0] = L'\\';
-  } else if (CountChars >= ExtendedPrefix16Len &&
-             ::wmemcmp(Data, ExtendedPrefix16, ExtendedPrefix16Len) == 0) {
+  } else if (CountChars >= LongPathPrefix16Len &&
+             ::wmemcmp(Data, LongPathPrefix16, LongPathPrefix16Len) == 0) {
     // Convert \\?\C:\foo to C:\foo
     CountChars -= 4;
     Data += 4;
@@ -125,7 +124,7 @@ std::error_code widenPath(const Twine &Path8, SmallVectorImpl<wchar_t> &Path16,
   }
 
   if ((Path16.size() + CurPathLen) < MaxPathLen ||
-      Path8Str.starts_with(ExtendedPrefix8))
+      Path8Str.starts_with(LongPathPrefix8))
     return std::error_code();
 
   if (!IsAbsolute) {
@@ -145,10 +144,10 @@ std::error_code widenPath(const Twine &Path8, SmallVectorImpl<wchar_t> &Path16,
 
   SmallString<2 * MAX_PATH> FullPath;
   if (RootName[1] != ':') { // Check if UNC.
-    FullPath.append(ExtendedUNCPrefix8);
+    FullPath.append(LongPathUNCPrefix8);
     FullPath.append(Path8Str.begin() + 2, Path8Str.end());
   } else {
-    FullPath.append(ExtendedPrefix8);
+    FullPath.append(LongPathPrefix8);
     FullPath.append(Path8Str);
   }
 
@@ -159,7 +158,7 @@ std::error_code makeLongFormPath(const Twine &Path8,
                                  llvm::SmallVectorImpl<char> &Result8) {
   SmallString<128> PathStorage;
   StringRef PathStr = Path8.toStringRef(PathStorage);
-  bool HadPrefix = PathStr.starts_with(ExtendedPrefix8);
+  bool HadPrefix = PathStr.starts_with(LongPathPrefix8);
 
   SmallVector<wchar_t, 128> Path16;
   if (std::error_code EC = widenPath(PathStr, Path16))
@@ -189,11 +188,11 @@ std::error_code makeLongFormPath(const Twine &Path8,
   // including the null-terminator.
   Long16.truncate(Len);
 
-  // Strip \\?\ or \\?\UNC\ extended length prefix if it wasn't part of the
+  // Strip \\?\ or \\?\UNC\ long length prefix if it wasn't part of the
   // original path.
   wchar_t *Data = Long16.data();
   if (!HadPrefix)
-    stripExtendedPrefix(Data, Len);
+    stripLongPathPrefix(Data, Len);
 
   return sys::windows::UTF16ToUTF8(Data, Len, Result8);
 }
@@ -478,7 +477,7 @@ static std::error_code realPathFromHandle(HANDLE H,
   // paths don't get canonicalized by file APIs.
   wchar_t *Data = Buffer.data();
   DWORD CountChars = Buffer.size();
-  stripExtendedPrefix(Data, CountChars);
+  stripLongPathPrefix(Data, CountChars);
 
   // Convert the result from UTF-16 to UTF-8.
   if (std::error_code EC = UTF16ToUTF8(Data, CountChars, RealPath))
diff --git a/llvm/unittests/Support/Path.cpp b/llvm/unittests/Support/Path.cpp
index 4408679737297..689dfe85aeb73 100644
--- a/llvm/unittests/Support/Path.cpp
+++ b/llvm/unittests/Support/Path.cpp
@@ -2490,7 +2490,7 @@ TEST_F(FileSystemTest, widenPath) {
 static llvm::Expected<bool> areShortNamesEnabled(llvm::StringRef Path8) {
   // Create a directory under Path8 with a name long enough that Windows will
   // provide a short 8.3 form name, if short 8.3 form names are enabled.
-  SmallString<256> Dir(Path8);
+  SmallString<MAX_PATH> Dir(Path8);
   path::append(Dir, "verylongdir");
   if (std::error_code EC = fs::create_directories(Dir))
     return llvm::errorCodeToError(EC);
@@ -2535,7 +2535,7 @@ static std::string getShortPathName(llvm::StringRef Path8) {
 
   ShortPath.truncate(Written);
 
-  SmallString<128> Utf8Result;
+  SmallString<MAX_PATH> Utf8Result;
   if (std::error_code EC = sys::windows::UTF16ToUTF8(
           ShortPath.data(), ShortPath.size(), Utf8Result))
     return {};
@@ -2550,7 +2550,7 @@ static bool sameEntity(llvm::StringRef P1, llvm::StringRef P2) {
   return !fs::getUniqueID(P1, ID1) && !fs::getUniqueID(P2, ID2) && ID1 == ID2;
 }
 
-/// Removes the Windows extended path prefix (\\?\ or \\?\UNC\) from the given
+/// Removes the Windows long path path prefix (\\?\ or \\?\UNC\) from the given
 /// UTF-8 path, if present.
 static std::string stripPrefix(llvm::StringRef P) {
   if (P.starts_with(R"(\\?\UNC\)"))
@@ -2588,33 +2588,53 @@ TEST_F(FileSystemTest, makeLongFormPath) {
   std::string DeepShort = stripPrefix(DeepShortWithPrefix);
 
   // Setup: Create a short 8.3 form path for the first-level directory.
-  SmallString<256> FirstLevel(TestDirectory);
+  SmallString<MAX_PATH> FirstLevel(TestDirectory);
   FirstLevel.append(OneDir);
   std::string Short = getShortPathName(FirstLevel);
   ASSERT_FALSE(Short.empty())
       << "Expected short 8.3 form path for test directory.";
 
+  // Setup: Create a short 8.3 form path with . and .. components for the
+  // first-level directory.
+  llvm::SmallString<MAX_PATH> WithDots(FirstLevel);
+  llvm::sys::path::append(WithDots, ".", "..", OneDir);
+  std::string DotAndDotDot = getShortPathName(WithDots);
+  ASSERT_FALSE(DotAndDotDot.empty())
+      << "Expected short 8.3 form path for test directory.";
+  auto ContainsDotAndDotDot = [](llvm::StringRef S) {
+    return S.contains("\\.\\") && S.contains("\\..\\");
+  };
+  ASSERT_TRUE(ContainsDotAndDotDot(DotAndDotDot))
+      << "Expected '.' and '..' components in: " << DotAndDotDot;
+
   // Case 1: Non-existent short 8.3 form path.
-  SmallString<128> NoExist("NotEre~1");
+  SmallString<MAX_PATH> NoExist("NotEre~1");
   ASSERT_FALSE(fs::exists(NoExist));
-  SmallString<128> NoExistResult;
+  SmallString<MAX_PATH> NoExistResult;
   EXPECT_TRUE(windows::makeLongFormPath(NoExist, NoExistResult));
   EXPECT_TRUE(NoExistResult.empty());
 
   // Case 2: Valid short 8.3 form path.
-  SmallString<128> ShortResult;
+  SmallString<MAX_PATH> ShortResult;
   ASSERT_FALSE(windows::makeLongFormPath(Short, ShortResult));
   EXPECT_TRUE(sameEntity(Short, ShortResult));
 
-  // Case 3: Deep short 8.3 form path without \\?\ prefix.
-  SmallString<128> DeepResult;
+  // Case 3: Valid . and .. short 8.3 form path.
+  SmallString<MAX_PATH> DotAndDotDotResult;
+  ASSERT_FALSE(windows::makeLongFormPath(DotAndDotDot, DotAndDotDotResult));
+  EXPECT_TRUE(sameEntity(DotAndDotDot, DotAndDotDotResult));
+  // Assert that '.' and '..' remain as path components.
+  ASSERT_TRUE(ContainsDotAndDotDot(DotAndDotDotResult));
+
+  // Case 4: Deep short 8.3 form path without \\?\ prefix.
+  SmallString<MAX_PATH> DeepResult;
   ASSERT_FALSE(windows::makeLongFormPath(DeepShort, DeepResult));
   EXPECT_TRUE(sameEntity(DeepShort, DeepResult));
   EXPECT_FALSE(StringRef(DeepResult).starts_with(R"(\\?\)"))
       << "Expected unprefixed result, got: " << DeepResult;
 
-  // Case 4: Deep short 8.3 form path with \\?\ prefix.
-  SmallString<128> DeepPrefixedResult;
+  // Case 5: Deep short 8.3 form path with \\?\ prefix.
+  SmallString<MAX_PATH> DeepPrefixedResult;
   ASSERT_FALSE(
       windows::makeLongFormPath(DeepShortWithPrefix, DeepPrefixedResult));
   EXPECT_TRUE(sameEntity(DeepShortWithPrefix, DeepPrefixedResult));

>From e1f82d5c009cfbf0cd1cf77831c3e7e3ddf36be4 Mon Sep 17 00:00:00 2001
From: Ben <ben.dunbobbin at sony.com>
Date: Mon, 2 Feb 2026 23:13:14 +0000
Subject: [PATCH 4/7] Add TOCTOU comment

---
 llvm/lib/Support/Windows/Path.inc | 1 +
 1 file changed, 1 insertion(+)

diff --git a/llvm/lib/Support/Windows/Path.inc b/llvm/lib/Support/Windows/Path.inc
index d1dd054fdd829..97dd75a74d18c 100644
--- a/llvm/lib/Support/Windows/Path.inc
+++ b/llvm/lib/Support/Windows/Path.inc
@@ -168,6 +168,7 @@ std::error_code makeLongFormPath(const Twine &Path8,
   llvm::SmallVector<wchar_t, 128> Long16;
   DWORD Len = static_cast<DWORD>(Path16.size());
 
+  // Loop instead of a double call to be defensive against TOCTOU races.
   do {
     Long16.resize_for_overwrite(Len);
 

>From 2bc5f142f8d3f6fc7ecbfcf59448af08c742ba79 Mon Sep 17 00:00:00 2001
From: Ben <ben.dunbobbin at sony.com>
Date: Tue, 10 Feb 2026 00:43:55 +0000
Subject: [PATCH 5/7] Ensure path is NUL-terminated before calling
 FindFirstFileW

---
 llvm/unittests/Support/Path.cpp | 1 +
 1 file changed, 1 insertion(+)

diff --git a/llvm/unittests/Support/Path.cpp b/llvm/unittests/Support/Path.cpp
index 689dfe85aeb73..54a8b87182efc 100644
--- a/llvm/unittests/Support/Path.cpp
+++ b/llvm/unittests/Support/Path.cpp
@@ -2499,6 +2499,7 @@ static llvm::Expected<bool> areShortNamesEnabled(llvm::StringRef Path8) {
   SmallVector<wchar_t, MAX_PATH> Path16;
   if (std::error_code EC = sys::windows::widenPath(Dir, Path16))
     return llvm::errorCodeToError(EC);
+  Path16.push_back(L'\0');
 
   WIN32_FIND_DATAW Data;
   HANDLE H = ::FindFirstFileW(Path16.data(), &Data);

>From d94058adca9c0f1c7ef90df07246db44da520bad Mon Sep 17 00:00:00 2001
From: Ben <ben.dunbobbin at sony.com>
Date: Tue, 10 Feb 2026 11:34:01 +0000
Subject: [PATCH 6/7] Revert "Ensure path is NUL-terminated before calling
 FindFirstFileW"

This reverts commit 2bc5f142f8d3f6fc7ecbfcf59448af08c742ba79.

This is not needed as widenPath does guarantee null termination.
---
 llvm/unittests/Support/Path.cpp | 1 -
 1 file changed, 1 deletion(-)

diff --git a/llvm/unittests/Support/Path.cpp b/llvm/unittests/Support/Path.cpp
index 54a8b87182efc..689dfe85aeb73 100644
--- a/llvm/unittests/Support/Path.cpp
+++ b/llvm/unittests/Support/Path.cpp
@@ -2499,7 +2499,6 @@ static llvm::Expected<bool> areShortNamesEnabled(llvm::StringRef Path8) {
   SmallVector<wchar_t, MAX_PATH> Path16;
   if (std::error_code EC = sys::windows::widenPath(Dir, Path16))
     return llvm::errorCodeToError(EC);
-  Path16.push_back(L'\0');
 
   WIN32_FIND_DATAW Data;
   HANDLE H = ::FindFirstFileW(Path16.data(), &Data);

>From e9786a69525686d71183b9909f75a2003fa840b9 Mon Sep 17 00:00:00 2001
From: Ben <ben.dunbobbin at sony.com>
Date: Tue, 10 Feb 2026 12:24:13 +0000
Subject: [PATCH 7/7] Be more explicit about FindFirstFileW error condition

---
 llvm/unittests/Support/Path.cpp | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/llvm/unittests/Support/Path.cpp b/llvm/unittests/Support/Path.cpp
index 689dfe85aeb73..08c0c55404d29 100644
--- a/llvm/unittests/Support/Path.cpp
+++ b/llvm/unittests/Support/Path.cpp
@@ -32,6 +32,7 @@
 #include "llvm/ADT/ArrayRef.h"
 #include "llvm/Support/Chrono.h"
 #include "llvm/Support/Windows/WindowsSupport.h"
+#include "llvm/Support/WindowsError.h"
 #include <fileapi.h>
 #include <windows.h>
 #include <winerror.h>
@@ -2503,9 +2504,7 @@ static llvm::Expected<bool> areShortNamesEnabled(llvm::StringRef Path8) {
   WIN32_FIND_DATAW Data;
   HANDLE H = ::FindFirstFileW(Path16.data(), &Data);
   if (H == INVALID_HANDLE_VALUE)
-    return llvm::make_error<llvm::StringError>(
-        "FindFirstFileW returned invalid handle",
-        llvm::inconvertibleErrorCode());
+    return llvm::errorCodeToError(llvm::mapWindowsError(::GetLastError()));
   ::FindClose(H);
 
   return (Data.cAlternateFileName[0] != L'\0');



More information about the llvm-commits mailing list