]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
fix: Improve MSVC .rsp file parsing (#1303)
authorJiri Hörner <44769714+ptc-jhoerner@users.noreply.github.com>
Thu, 29 Jun 2023 19:03:59 +0000 (21:03 +0200)
committerJoel Rosdahl <joel@rosdahl.net>
Sun, 27 Aug 2023 10:39:13 +0000 (12:39 +0200)
(cherry picked from commit 5e0e46eb54461ef20f679028a725638b309fd3a9)

src/Args.cpp
unittest/test_Args.cpp

index da041bcb850c574b3babb99a34129efb46bc9719..2b05673ba0b8671e15888393e582e4015ac99826 100644 (file)
@@ -60,7 +60,7 @@ Args::from_atfile(const std::string& filename, AtFileFormat format)
   auto pos = argtext->c_str();
   std::string argbuf;
   argbuf.resize(argtext->length() + 1);
-  auto argpos = argbuf.begin();
+  auto argpos = argbuf.data();
 
   // Used to track quoting state; if \0 we are not inside quotes. Otherwise
   // stores the quoting character that started it for matching the end quote.
@@ -69,17 +69,40 @@ Args::from_atfile(const std::string& filename, AtFileFormat format)
   while (true) {
     switch (*pos) {
     case '\\':
-      pos++;
       switch (format) {
       case AtFileFormat::gcc:
+        pos++;
         if (*pos == '\0') {
           continue;
         }
         break;
       case AtFileFormat::msvc:
-        if (*pos != '"') {
-          pos--;
+        size_t count = 0;
+        while (*pos == '\\') {
+          count++;
+          pos++;
         }
+        if (*pos == '"') {
+          if (count == 1) {
+            // simple escape \"
+            break;
+          }
+          if (count % 2 != 0) {
+            // If an odd number of backslashes is followed by a double quotation
+            // mark, one backslash is placed in the argv array for every pair of
+            // backslashes, and the double quotation mark is "escaped" by the
+            // remaining backslash
+            pos--;
+          }
+          std::memset(argpos, '\\', count / 2);
+          argpos += count / 2;
+        } else {
+          // Backslashes are interpreted literally, unless they immediately
+          // precede a double quotation mark.
+          std::memset(argpos, '\\', count);
+          argpos += count;
+        }
+        continue;
         break;
       }
       break;
@@ -95,6 +118,13 @@ Args::from_atfile(const std::string& filename, AtFileFormat format)
         if (quoting == *pos) {
           quoting = '\0';
           pos++;
+          if (format == AtFileFormat::msvc && *pos == '"') {
+            // Any double-quote directly following a closing quote is treated as
+            // (or as part of) plain unwrapped text that is adjacent to the
+            // double-quoted group
+            // https://stackoverflow.com/questions/7760545/escape-double-quotes-in-parameter
+            break;
+          }
           continue;
         } else {
           break;
@@ -120,7 +150,7 @@ Args::from_atfile(const std::string& filename, AtFileFormat format)
       if (argbuf[0] != '\0') {
         args.push_back(argbuf.substr(0, argbuf.find('\0')));
       }
-      argpos = argbuf.begin();
+      argpos = argbuf.data();
       if (*pos == '\0') {
         return args;
       } else {
index f5d9ae7b2dfd60bfe2b39f575f9d8468fd4cafbe..388213fda8be892cba3f1f67086bd89f82ce5d30 100644 (file)
@@ -139,29 +139,118 @@ TEST_CASE("Args::from_atfile")
     CHECK(args[6] == "seve\nth");
   }
 
-  SUBCASE("Only escape double quote in alternate format")
+  SUBCASE("Ignore single quote in MSVC format")
   {
-    util::write_file("at_file", "\"\\\"\\a\\ \\b\\\"\"\\");
+    util::write_file("at_file", "'a b'");
+    args = *Args::from_atfile("at_file", Args::AtFileFormat::msvc);
+    CHECK(args.size() == 2);
+    CHECK(args[0] == "'a");
+    CHECK(args[1] == "b'");
+  }
+
+  SUBCASE("Backslash as directory separator in MSVC format")
+  {
+    util::write_file("at_file", R"("-DDIRSEP='A\B\C'")");
     args = *Args::from_atfile("at_file", Args::AtFileFormat::msvc);
     CHECK(args.size() == 1);
-    CHECK(args[0] == "\"\\a\\ \\b\"\\");
+    CHECK(args[0] == R"(-DDIRSEP='A\B\C')");
   }
 
-  SUBCASE("Ignore single quote in alternate format")
+  SUBCASE("Backslash before quote in MSVC format")
   {
-    util::write_file("at_file", "'a b'");
+    util::write_file("at_file", R"(/Fo"N.dir\Release\\")");
+    args = *Args::from_atfile("at_file", Args::AtFileFormat::msvc);
+    CHECK(args.size() == 1);
+    CHECK(args[0] == R"(/FoN.dir\Release\)");
+  }
+
+  SUBCASE("Arguments on multiple lines in MSVC format")
+  {
+    util::write_file("at_file", "a\nb");
     args = *Args::from_atfile("at_file", Args::AtFileFormat::msvc);
     CHECK(args.size() == 2);
-    CHECK(args[0] == "'a");
-    CHECK(args[1] == "b'");
+    CHECK(args[0] == "a");
+    CHECK(args[1] == "b");
   }
 
-  SUBCASE("Do not escape backslash in alternate format")
+  SUBCASE("Tricky quoting in MSVC format (#1247)")
   {
-    util::write_file("at_file", "\"-DDIRSEP='\\\\'\"");
+    util::write_file(
+      "at_file",
+      R"(\ \\ '\\' "\\" '"\\"' "'\\'" '''\\''' ''"\\"'' '"'\\'"' '""\\""' "''\\''" "'"\\"'" ""'\\'"" """\\""" )"
+      R"(\'\' '\'\'' "\'\'" ''\'\''' '"\'\'"' "'\'\''" ""\'\'"" '''\'\'''' ''"\'\'"'' '"'\'\''"' '""\'\'""' "''\'\'''" "'"\'\'"'" ""'\'\''"" """\'\'""" )"
+      R"(\"\" '\"\"' "\"\"" ''\"\"'' '"\"\""' "'\"\"'" ""\"\""" '''\"\"''' ''"\"\""'' '"'\"\"'"' '""\"\"""' "''\"\"''" "'"\"\""'" ""'\"\"'"" """\"\"""")");
     args = *Args::from_atfile("at_file", Args::AtFileFormat::msvc);
-    CHECK(args.size() == 1);
-    CHECK(args[0] == "-DDIRSEP='\\\\'");
+    CHECK(args.size() == 44);
+    CHECK(args[0] == R"(\)");
+    CHECK(args[1] == R"(\\)");
+    CHECK(args[2] == R"('\\')");
+    CHECK(args[3] == R"(\)");
+    CHECK(args[4] == R"('\')");
+    CHECK(args[5] == R"('\\')");
+    CHECK(args[6] == R"('''\\''')");
+    CHECK(args[7] == R"(''\'')");
+    CHECK(args[8] == R"(''\\'')");
+    CHECK(args[9] == R"('\')");
+    CHECK(args[10] == R"(''\\'')");
+    CHECK(args[11] == R"('\')");
+    CHECK(args[12] == R"('\\')");
+    CHECK(args[13] == R"("\")");
+    CHECK(args[14] == R"(\'\')");
+    CHECK(args[15] == R"('\'\'')");
+    CHECK(args[16] == R"(\'\')");
+    CHECK(args[17] == R"(''\'\''')");
+    CHECK(args[18] == R"('\'\'')");
+    CHECK(args[19] == R"('\'\'')");
+    CHECK(args[20] == R"(\'\')");
+    CHECK(args[21] == R"('''\'\'''')");
+    CHECK(args[22] == R"(''\'\''')");
+    CHECK(args[23] == R"(''\'\''')");
+    CHECK(args[24] == R"('\'\'')");
+    CHECK(args[25] == R"(''\'\''')");
+    CHECK(args[26] == R"('\'\'')");
+    CHECK(args[27] == R"('\'\'')");
+    CHECK(args[28] == R"("\'\'")");
+    CHECK(args[29] == R"("")");
+    CHECK(args[30] == R"('""')");
+    CHECK(args[31] == R"("")");
+    CHECK(args[32] == R"(''""'')");
+    CHECK(args[33] == R"('""')");
+    CHECK(args[34] == R"('""')");
+    CHECK(args[35] == R"("")");
+    CHECK(args[36] == R"('''""''')");
+    CHECK(args[37] == R"(''""'')");
+    CHECK(args[38] == R"(''""'')");
+    CHECK(args[39] == R"('""')");
+    CHECK(args[40] == R"(''""'')");
+    CHECK(args[41] == R"('""')");
+    CHECK(args[42] == R"('""')");
+    CHECK(args[43] == R"("""")");
+  }
+
+  SUBCASE("Quoting from Microsoft documentation in MSVC format")
+  {
+    // See
+    // https://learn.microsoft.com/en-us/previous-versions//17w5ykft(v=vs.85)?redirectedfrom=MSDN
+    util::write_file("at_file",
+                     R"("abc" d e )"
+                     R"(a\\\b d"e f"g h )"
+                     R"(a\\\"b c d )"
+                     R"(a\\\\"b c" d e)");
+    args = *Args::from_atfile("at_file", Args::AtFileFormat::msvc);
+    CHECK(args.size() == 12);
+    CHECK(args[0] == R"(abc)");
+    CHECK(args[1] == R"(d)");
+    CHECK(args[2] == R"(e)");
+    CHECK(args[3] == R"(a\\\b)");
+    CHECK(args[4] == R"(de fg)");
+    CHECK(args[5] == R"(h)");
+    CHECK(args[6] == R"(a\"b)");
+    CHECK(args[7] == R"(c)");
+    CHECK(args[8] == R"(d)");
+    CHECK(args[9] == R"(a\\b c)");
+    CHECK(args[10] == R"(d)");
+    CHECK(args[11] == R"(e)");
   }
 }