From: Jiri Hörner <44769714+ptc-jhoerner@users.noreply.github.com> Date: Thu, 29 Jun 2023 19:03:59 +0000 (+0200) Subject: fix: Improve MSVC .rsp file parsing (#1303) X-Git-Tag: v4.9~162 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5e0e46eb54461ef20f679028a725638b309fd3a9;p=thirdparty%2Fccache.git fix: Improve MSVC .rsp file parsing (#1303) --- diff --git a/src/Args.cpp b/src/Args.cpp index da041bcb8..2b05673ba 100644 --- a/src/Args.cpp +++ b/src/Args.cpp @@ -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 { diff --git a/unittest/test_Args.cpp b/unittest/test_Args.cpp index f5d9ae7b2..388213fda 100644 --- a/unittest/test_Args.cpp +++ b/unittest/test_Args.cpp @@ -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)"); } }