From 2ab910a35f7d45dd3c0d4874629168841b621ac2 Mon Sep 17 00:00:00 2001 From: Nikita Nemkin <nikita@nemkin.ru> Date: Wed, 5 Feb 2025 21:44:20 +0500 Subject: [PATCH] SystemTools: Improve repeated slash handling in ConvertToUnixSlashes Collapse repeated slashes, but preserve exactly two leading slashes. POSIX reserves exactly two leading slashes for implementation-defined behavior. On Windows and Cygwin they mark UNC paths. Improve test coverage of ConvertToUnixSlashes. Issue: cmake/cmake#9182 --- SystemTools.cxx | 45 +++++++++++++-------------------------------- testSystemTools.cxx | 14 ++++++++++++++ 2 files changed, 27 insertions(+), 32 deletions(-) diff --git a/SystemTools.cxx b/SystemTools.cxx index 39846c0..dcf2b66 100644 --- a/SystemTools.cxx +++ b/SystemTools.cxx @@ -2087,38 +2087,21 @@ void SystemTools::ConvertToUnixSlashes(std::string& path) } char const* pathCString = path.c_str(); - bool hasDoubleSlash = false; #ifdef __VMS ConvertVMSToUnix(path); #else - char const* pos0 = pathCString; - for (std::string::size_type pos = 0; *pos0; ++pos) { - if (*pos0 == '\\') { - path[pos] = '/'; - } - - // Also, reuse the loop to check for slash followed by another slash - if (!hasDoubleSlash && *(pos0 + 1) == '/' && *(pos0 + 2) == '/') { -# ifdef _WIN32 - // However, on windows if the first characters are both slashes, - // then keep them that way, so that network paths can be handled. - if (pos > 0) { - hasDoubleSlash = true; - } -# else - hasDoubleSlash = true; -# endif - } - - pos0++; - } - - if (hasDoubleSlash) { - SystemTools::ReplaceString(path, "//", "/"); - } + // replace backslashes + std::replace(path.begin(), path.end(), '\\', '/'); + + // collapse repeated slashes, except exactly two leading slashes are + // meaningful and must be preserved. + bool hasDoubleSlash = path[0] == '/' && path[1] == '/' && path[2] != '/'; + auto uniqueEnd = std::unique( + path.begin() + hasDoubleSlash, path.end(), + [](char c1, char c2) -> bool { return c1 == '/' && c1 == c2; }); + path.erase(uniqueEnd, path.end()); #endif - // remove any trailing slash // if there is a tilda ~ then replace it with HOME pathCString = path.c_str(); if (pathCString[0] == '~' && @@ -2140,13 +2123,11 @@ void SystemTools::ConvertToUnixSlashes(std::string& path) } } #endif - // remove trailing slash if the path is more than - // a single / - pathCString = path.c_str(); + // remove trailing slash, but preserve the root slash and the slash + // after windows drive letter (c:/). size_t size = path.size(); if (size > 1 && path.back() == '/') { - // if it is c:/ then do not remove the trailing slash - if (!((size == 3 && pathCString[1] == ':'))) { + if (!(size == 3 && path[1] == ':') && path[size - 2] != '/') { path.resize(size - 1); } } diff --git a/testSystemTools.cxx b/testSystemTools.cxx index 055ffd1..0704458 100644 --- a/testSystemTools.cxx +++ b/testSystemTools.cxx @@ -53,6 +53,20 @@ static char const* toUnixPaths[][2] = { { "\\\\usr\\local\\bin\\passwd", "//usr/local/bin/passwd" }, { "\\\\usr\\lo cal\\bin\\pa sswd", "//usr/lo cal/bin/pa sswd" }, { "\\\\usr\\lo\\ cal\\bin\\pa\\ sswd", "//usr/lo/ cal/bin/pa/ sswd" }, + { "\\", "/" }, + { "/", "/" }, + { "\\\\", "//" }, + { "//", "//" }, + { "\\\\\\", "/" }, + { "///", "/" }, + { "C:\\", "C:/" }, + { "C:\\\\", "C:/" }, + { "C:\\\\\\", "C:/" }, + { "\\\\UNC\\path", "//UNC/path" }, + { "//UNC/path", "//UNC/path" }, + { "\\\\\\triple\\\\back\\\\\\slash\\\\\\", "/triple/back/slash" }, + { "///triple//back///slash///", "/triple/back/slash" }, + { "///////ex treme/////////", "/ex treme" }, { nullptr, nullptr } }; -- GitLab