]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
Fix result_name_from_depfile by parsing depfile in Makefile syntax (#722)
authorYoshimasa Niwa <niw@niw.at>
Wed, 18 Nov 2020 20:18:18 +0000 (12:18 -0800)
committerGitHub <noreply@github.com>
Wed, 18 Nov 2020 20:18:18 +0000 (21:18 +0100)
src/ccache.cpp
src/ccache.hpp
unittest/test_ccache.cpp

index 6b0b822c4dc00e9f49a2b568fea2052bc54a4a02..95c443b4a046a54a78f8501778dd05c44034f6f6 100644 (file)
@@ -707,6 +707,82 @@ use_relative_paths_in_depfile(const Context& ctx)
   }
 }
 
+static inline bool
+is_blank(const std::string& s)
+{
+  return std::all_of(s.begin(), s.end(), [](char c) { return isspace(c); });
+}
+
+std::vector<std::string>
+parse_depfile(string_view file_content)
+{
+  std::vector<std::string> result;
+
+  // A depfile is formatted with Makefile syntax.
+  // This is not perfect parser, however enough for parsing a regular depfile.
+  const size_t length = file_content.size();
+  std::string token;
+  size_t p{0};
+  while (p < length) {
+    // Each token is separated by spaces.
+    if (isspace(file_content[p])) {
+      while (p < length && isspace(file_content[p])) {
+        p++;
+      }
+      if (!is_blank(token)) {
+        result.push_back(token);
+      }
+      token.clear();
+      continue;
+    }
+
+    char c{file_content[p]};
+    switch (c) {
+    case '\\':
+      if (p + 1 < length) {
+        const char next{file_content[p + 1]};
+        switch (next) {
+        // A backspace can be followed by next characters and leave them as-is.
+        case '\\':
+        case '#':
+        case ':':
+        case ' ':
+        case '\t':
+          c = next;
+          p++;
+          break;
+        // For this parser, it can treat a backslash-newline as just a space.
+        // Therefore simply skip a backslash.
+        case '\n':
+          p++;
+          continue;
+        }
+      }
+      break;
+    case '$':
+      if (p + 1 < length) {
+        const char next{file_content[p + 1]};
+        switch (next) {
+        // A dollar sign can be followed by a dollar sign and leave it as-is.
+        case '$':
+          c = next;
+          p++;
+          break;
+        }
+      }
+      break;
+    }
+
+    token.push_back(c);
+    p++;
+  }
+  if (!is_blank(token)) {
+    result.push_back(token);
+  }
+
+  return result;
+}
+
 // Extract the used includes from the dependency file. Note that we cannot
 // distinguish system headers from other includes here.
 static optional<Digest>
@@ -721,8 +797,8 @@ result_name_from_depfile(Context& ctx, Hash& hash)
     return nullopt;
   }
 
-  for (string_view token : Util::split_into_views(file_content, " \t\r\n")) {
-    if (token == "\\" || token.ends_with(":")) {
+  for (string_view token : parse_depfile(file_content)) {
+    if (token.ends_with(":")) {
       continue;
     }
     if (!ctx.has_absolute_include_headers) {
index 924937adfc83b6581a89a7fc2bf3563be2679c63..2a74a699585238e777cbf2ad9d2ba4091999d056 100644 (file)
@@ -28,6 +28,7 @@
 
 #include <functional>
 #include <string>
+#include <vector>
 
 class Context;
 
@@ -64,3 +65,4 @@ void find_compiler(Context& ctx,
 CompilerType guess_compiler(nonstd::string_view path);
 nonstd::optional<std::string>
 rewrite_dep_file_paths(const Context& ctx, const std::string& file_content);
+std::vector<std::string> parse_depfile(nonstd::string_view file_content);
index ad77009f4bb28455d7b712f2256df0216d7d4379..3601e4e41b79f3618353dd31315c982d4bf1f5e1 100644 (file)
@@ -236,4 +236,128 @@ TEST_CASE("rewrite_dep_file_paths")
   }
 }
 
+TEST_CASE("parse_depfile")
+{
+  SUBCASE("Parse empty depfile")
+  {
+    std::vector<std::string> result = parse_depfile("");
+    CHECK(result.size() == 0);
+  }
+
+  SUBCASE("Parse simple depfile")
+  {
+    std::vector<std::string> result = parse_depfile("cat.o: meow meow purr");
+    REQUIRE(result.size() == 4);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow");
+    CHECK(result[2] == "meow");
+    CHECK(result[3] == "purr");
+  }
+
+  SUBCASE("Parse depfile with a dollar sign followed by a dollar sign")
+  {
+    std::vector<std::string> result = parse_depfile("cat.o: meow$$");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow$");
+  }
+
+  SUBCASE("Parse depfile with a dollar sign followed by an alphabet")
+  {
+    std::vector<std::string> result = parse_depfile("cat.o: meow$w");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow$w");
+  }
+
+  SUBCASE("Parse depfile with a backslash followed by a number sign or a colon")
+  {
+    std::vector<std::string> result = parse_depfile("cat.o: meow\\# meow\\:");
+    REQUIRE(result.size() == 3);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow#");
+    CHECK(result[2] == "meow:");
+  }
+
+  SUBCASE("Parse depfile with a backslash followed by an alphabet")
+  {
+    std::vector<std::string> result = parse_depfile("cat.o: meow\\w purr\\r");
+    REQUIRE(result.size() == 3);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow\\w");
+    CHECK(result[2] == "purr\\r");
+  }
+
+  SUBCASE("Parse depfile with a backslash followed by a space or a tab")
+  {
+    std::vector<std::string> result =
+      parse_depfile("cat.o: meow\\ meow purr\\\tpurr");
+    REQUIRE(result.size() == 3);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow meow");
+    CHECK(result[2] == "purr\tpurr");
+  }
+
+  SUBCASE("Parse depfile with backslashes followed by a space or a tab")
+  {
+    std::vector<std::string> result =
+      parse_depfile("cat.o: meow\\\\\\ meow purr\\\\ purr");
+    REQUIRE(result.size() == 4);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow\\ meow");
+    CHECK(result[2] == "purr\\");
+    CHECK(result[3] == "purr");
+  }
+
+  SUBCASE("Parse depfile with a backslash newline")
+  {
+    std::vector<std::string> result =
+      parse_depfile("cat.o: meow\\\nmeow\\\n purr\\\n\tpurr");
+    REQUIRE(result.size() == 5);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow");
+    CHECK(result[2] == "meow");
+    CHECK(result[3] == "purr");
+    CHECK(result[4] == "purr");
+  }
+
+  SUBCASE("Parse depfile with a new line")
+  {
+    // This is invalid depfile because it has multiple lines without backslash,
+    // which is not valid in Makefile syntax.
+    // However, parse_depfile is parsing it to each token, which is expected.
+    std::vector<std::string> result =
+      parse_depfile("cat.o: meow\nmeow\npurr\n");
+    REQUIRE(result.size() == 4);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow");
+    CHECK(result[2] == "meow");
+    CHECK(result[3] == "purr");
+  }
+
+  SUBCASE("Parse depfile with a trailing dollar sign")
+  {
+    std::vector<std::string> result = parse_depfile("cat.o: meow$");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow$");
+  }
+
+  SUBCASE("Parse depfile with a trailing backslash")
+  {
+    std::vector<std::string> result = parse_depfile("cat.o: meow\\");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow\\");
+  }
+
+  SUBCASE("Parse depfile with a trailing backslash newline")
+  {
+    std::vector<std::string> result = parse_depfile("cat.o: meow\\\n");
+    REQUIRE(result.size() == 2);
+    CHECK(result[0] == "cat.o:");
+    CHECK(result[1] == "meow");
+  }
+}
+
 TEST_SUITE_END();