]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
fix: Make conversion to relative paths more reliable on Windows (#1011)
authorMarius Zwicker <marius@mlba-team.de>
Sun, 27 Feb 2022 18:56:44 +0000 (19:56 +0100)
committerGitHub <noreply@github.com>
Sun, 27 Feb 2022 18:56:44 +0000 (19:56 +0100)
src/Util.cpp
src/Util.hpp
src/argprocessing.cpp
src/util/path.cpp
src/util/path.hpp
unittest/test_Util.cpp
unittest/test_util_path.cpp

index a3de8dcde850609d083d903871469727739f3727..007006cb2485cab4241a5df7c3cf6dca2ac7860c 100644 (file)
@@ -233,6 +233,31 @@ base_name(string_view path)
   return n == std::string::npos ? path : path.substr(n + 1);
 }
 
+bool
+is_absolute_path_with_prefix(nonstd::string_view path, size_t& split_pos)
+{
+#ifdef _WIN32
+  const char delim[] = "/\\";
+#else
+  const char delim[] = "/";
+#endif
+  split_pos = path.find_first_of(delim);
+  if (split_pos != std::string::npos) {
+#ifdef _WIN32
+    // -I/C:/foo and -I/c/foo will already be handled by delim_pos
+    // correctly resulting in -I and /C:/foo or /c/foo respectively.
+    // -IC:/foo will not as we would get -IC: and /foo
+    if (split_pos > 0 && path[split_pos - 1] == ':') {
+      split_pos = split_pos - 2;
+    }
+#endif
+    // this is not redundant on some platforms so nothing to simplify
+    // NOLINTNEXTLINE(readability-simplify-boolean-expr)
+    return true;
+  }
+  return false;
+}
+
 std::string
 change_extension(string_view path, string_view new_ext)
 {
@@ -832,7 +857,7 @@ make_relative_path(const std::string& base_dir,
                    const std::string& apparent_cwd,
                    nonstd::string_view path)
 {
-  if (base_dir.empty() || !util::starts_with(path, base_dir)) {
+  if (base_dir.empty() || !util::path_starts_with(path, base_dir)) {
     return std::string(path);
   }
 
index 6e0aea833453a7d268df63ddfd4ba6118d48c214..b92a3d5ca4aa759f1edbfc69e8edc4baf490b7f5 100644 (file)
@@ -46,6 +46,10 @@ enum class UnlinkLog { log_failure, ignore_failure };
 // Get base name of path.
 nonstd::string_view base_name(nonstd::string_view path);
 
+// Determine if `path` is an absolute path with prefix, returning the split
+// point
+bool is_absolute_path_with_prefix(nonstd::string_view path, size_t& split_pos);
+
 // Get an integer value from bytes in big endian order.
 //
 // Parameters:
index 166681ff414e96e92c7e77cec4759ef96074a059..345cb9fa5e6fc06d562a03fc5047da645e4490b2 100644 (file)
@@ -919,8 +919,8 @@ process_arg(const Context& ctx,
 
   // Potentially rewrite concatenated absolute path argument to relative.
   if (args[i][0] == '-') {
-    size_t slash_pos = args[i].find('/');
-    if (slash_pos != std::string::npos) {
+    size_t slash_pos = 0;
+    if (Util::is_absolute_path_with_prefix(args[i], slash_pos)) {
       std::string option = args[i].substr(0, slash_pos);
       if (compopt_takes_concat_arg(option) && compopt_takes_path(option)) {
         auto relpath =
index 756aef37779d0d92736ed5aeb50f4f94e0b15ab6..ce2c6b9eb610ba3ecf538167b2af8aa7d6054295 100644 (file)
@@ -41,6 +41,35 @@ is_absolute_path(nonstd::string_view path)
   return !path.empty() && path[0] == '/';
 }
 
+bool
+path_starts_with(nonstd::string_view path, nonstd::string_view prefix)
+{
+  for (size_t i = 0, j = 0; i < path.length() && j < prefix.length();
+       ++i, ++j) {
+#ifdef _WIN32
+    // skip escaped backslashes \\\\ as seen by the preprocessor
+    if (i > 0 && path[i] == '\\' && path[i - 1] == '\\') {
+      ++i;
+    }
+    if (j > 0 && prefix[j] == '\\' && prefix[j - 1] == '\\') {
+      ++j;
+    }
+
+    // handle back and forward slashes as equal
+    if (path[i] == '/' && prefix[j] == '\\') {
+      continue;
+    }
+    if (path[i] == '\\' && prefix[j] == '/') {
+      continue;
+    }
+#endif
+    if (path[i] != prefix[j]) {
+      return false;
+    }
+  }
+  return true;
+}
+
 std::vector<std::string>
 split_path_list(nonstd::string_view path_list)
 {
index be5a999e30d5c78d58a17cfa877a31a9e0d5a953..51fcd58153f7fb43e69cb9766b4f47538647cb38 100644 (file)
@@ -33,6 +33,10 @@ bool is_absolute_path(nonstd::string_view path);
 // Return whether `path` includes at least one directory separator.
 bool is_full_path(nonstd::string_view path);
 
+// Return whether `path` starts with `prefix` considering path specifics on
+// Windows
+bool path_starts_with(nonstd::string_view path, nonstd::string_view prefix);
+
 // Split a list of paths (such as the content of $PATH on Unix platforms or
 // %PATH% on Windows platforms) into paths.
 std::vector<std::string> split_path_list(nonstd::string_view path_list);
index 3d59b71fe742ad6789c51b7396a19d88bfb622f9..f1945d63a1c3239166272a801b56421cafb76c58 100644 (file)
@@ -52,6 +52,27 @@ TEST_CASE("Util::base_name")
   CHECK(Util::base_name("/foo/bar/f.txt") == "f.txt");
 }
 
+TEST_CASE("Util::is_absolute_path_with_prefix")
+{
+  size_t delim_pos = 0;
+  CHECK(Util::is_absolute_path_with_prefix("-I/c/foo", delim_pos));
+  CHECK(delim_pos == 2);
+  CHECK(Util::is_absolute_path_with_prefix("-W,path/c/foo", delim_pos));
+  CHECK(delim_pos == 7);
+  CHECK(!Util::is_absolute_path_with_prefix("-DMACRO", delim_pos));
+#ifdef _WIN32
+  CHECK(Util::is_absolute_path_with_prefix("-I/C:/foo", delim_pos));
+  CHECK(delim_pos == 2);
+  CHECK(Util::is_absolute_path_with_prefix("-IC:/foo", delim_pos));
+  CHECK(delim_pos == 2);
+  CHECK(Util::is_absolute_path_with_prefix("-W,path/c:/foo", delim_pos));
+  CHECK(delim_pos == 7);
+  CHECK(Util::is_absolute_path_with_prefix("-W,pathc:/foo", delim_pos));
+  CHECK(delim_pos == 7);
+  CHECK(!Util::is_absolute_path_with_prefix("-opt:value", delim_pos));
+#endif
+}
+
 TEST_CASE("Util::big_endian_to_int")
 {
   uint8_t bytes[8] = {0x70, 0x9e, 0x9a, 0xbc, 0xd6, 0x54, 0x4b, 0xca};
@@ -489,6 +510,14 @@ TEST_CASE("Util::make_relative_path")
       make_relative_path(
         actual_cwd.substr(0, 3), actual_cwd, apparent_cwd, actual_cwd + "/x")
       == "./x");
+    CHECK(
+      make_relative_path(
+        actual_cwd.substr(0, 3), actual_cwd, apparent_cwd, actual_cwd + "\\x")
+      == ".\\x");
+    CHECK(
+      make_relative_path(
+        actual_cwd.substr(0, 3), actual_cwd, apparent_cwd, actual_cwd + "\\\\x")
+      == ".\\x");
 #else
     CHECK(make_relative_path("/", actual_cwd, apparent_cwd, actual_cwd + "/x")
           == "./x");
index b069041cc3e8d6f12ca21937c7cbb02cdace8760..0bdc7b88f35b5aaa403a08186a1d558b70231921 100644 (file)
@@ -115,3 +115,21 @@ TEST_CASE("util::to_absolute_path_no_drive")
   CHECK(util::to_absolute_path_no_drive("../foo/bar")
         == FMT("{}/foo/bar", Util::dir_name(cwd)));
 }
+
+TEST_CASE("util::path_starts_with")
+{
+  CHECK(util::path_starts_with("/foo/bar", "/foo"));
+  CHECK(!util::path_starts_with("/batz/bar", "/foo"));
+  CHECK(!util::path_starts_with("/foo/bar", "/foo/baz"));
+  CHECK(!util::path_starts_with("/beh/foo", "/foo"));
+#ifdef _WIN32
+  CHECK(util::path_starts_with("C:/foo/bar", "C:\\foo"));
+  CHECK(util::path_starts_with("C:/foo/bar", "C:\\\\foo"));
+  CHECK(util::path_starts_with("C:\\foo\\bar", "C:/foo"));
+  CHECK(util::path_starts_with("C:\\\\foo\\\\bar", "C:/foo"));
+  CHECK(!util::path_starts_with("C:\\foo\\bar", "/foo/baz"));
+  CHECK(!util::path_starts_with("C:\\foo\\bar", "C:/foo/baz"));
+  CHECK(!util::path_starts_with("C:\\beh\\foo", "/foo"));
+  CHECK(!util::path_starts_with("C:\\beh\\foo", "C:/foo"));
+#endif
+}
\ No newline at end of file