From 981abc892c7f0934ae839d5de17f19aeb97d50dc Mon Sep 17 00:00:00 2001 From: Asahi Lina Date: Fri, 20 Dec 2024 00:34:16 +0900 Subject: [PATCH] FHU: Fix shebang line parsing This function is used to parse shebang lines, and Linux does not parse shebang lines like shell command lines. Instead, it only splits it into an interpreter and an optional single argument, with leading/trailing whitespace stripped. --- .../FEXHeaderUtils/StringArgumentParser.h | 43 +++++++------ unittests/APITests/ArgumentParser.cpp | 64 ++++++++++++------- 2 files changed, 65 insertions(+), 42 deletions(-) diff --git a/FEXHeaderUtils/FEXHeaderUtils/StringArgumentParser.h b/FEXHeaderUtils/FEXHeaderUtils/StringArgumentParser.h index 2ddfc3f265..661444d5a7 100644 --- a/FEXHeaderUtils/FEXHeaderUtils/StringArgumentParser.h +++ b/FEXHeaderUtils/FEXHeaderUtils/StringArgumentParser.h @@ -8,32 +8,39 @@ namespace FHU { /** - * @brief Parses a string of arguments, returning a vector of string_views. + * @brief Parses a shebang string, returning a vector of string_views. * - * @param ArgumentString The string of arguments to parse + * @param ArgumentString The shebang string to parse * - * @return The array of parsed arguments + * @return The array of parsed elements */ static inline fextl::vector ParseArgumentsFromString(const std::string_view ArgumentString) { fextl::vector Arguments; - auto Begin = ArgumentString.begin(); - auto ArgEnd = Begin; - const auto End = ArgumentString.end(); - while (ArgEnd != End && Begin != End) { - // The end of an argument ends with a space or the end of the interpreter line. - ArgEnd = std::find(Begin, End, ' '); - - if (Begin != ArgEnd) { - const auto View = std::string_view(Begin, ArgEnd - Begin); - if (!View.empty()) { - Arguments.emplace_back(View); - } - } - - Begin = ArgEnd + 1; + const auto SPACE = " \f\n\r\t\v"; + + auto InterpBegin = ArgumentString.find_first_not_of(SPACE); + if (InterpBegin == std::string::npos) { + return Arguments; + } + + auto InterpLen = ArgumentString.substr(InterpBegin).find_first_of(SPACE); + Arguments.emplace_back(ArgumentString.substr(InterpBegin, InterpLen)); + if (InterpLen == std::string::npos) { + return Arguments; } + auto Arg = ArgumentString.substr(InterpBegin + InterpLen); + auto ArgBegin = Arg.find_first_not_of(SPACE); + if (ArgBegin == std::string::npos) { + return Arguments; + } + + Arg = Arg.substr(ArgBegin); + + auto ArgEnd = Arg.find_last_not_of(SPACE); + Arguments.emplace_back(Arg.substr(0, ArgEnd + 1)); + return Arguments; } } // namespace FHU diff --git a/unittests/APITests/ArgumentParser.cpp b/unittests/APITests/ArgumentParser.cpp index c67932c7f5..c1cc16ca5d 100644 --- a/unittests/APITests/ArgumentParser.cpp +++ b/unittests/APITests/ArgumentParser.cpp @@ -5,11 +5,9 @@ TEST_CASE("Basic") { const auto ArgString = "Test a b c"; auto Args = FHU::ParseArgumentsFromString(ArgString); - REQUIRE(Args.size() == 4); + REQUIRE(Args.size() == 2); CHECK(Args.at(0) == "Test"); - CHECK(Args.at(1) == "a"); - CHECK(Args.at(2) == "b"); - CHECK(Args.at(3) == "c"); + CHECK(Args.at(1) == "a b c"); } TEST_CASE("Basic - Empty") { @@ -24,52 +22,70 @@ TEST_CASE("Basic - Empty spaces") { REQUIRE(Args.size() == 0); } +TEST_CASE("Basic - Whitespace") { + const auto ArgString = " \t \f \r \n \v "; + auto Args = FHU::ParseArgumentsFromString(ArgString); + REQUIRE(Args.size() == 0); +} + +TEST_CASE("Basic - Interpreter only") { + const auto ArgString = "Test"; + auto Args = FHU::ParseArgumentsFromString(ArgString); + REQUIRE(Args.size() == 1); + CHECK(Args.at(0) == "Test"); +} + +TEST_CASE("Basic - Interpreter only with spaces") { + const auto ArgString = " Test "; + auto Args = FHU::ParseArgumentsFromString(ArgString); + REQUIRE(Args.size() == 1); + CHECK(Args.at(0) == "Test"); +} + TEST_CASE("Basic - Space at start") { const auto ArgString = " Test a b c"; auto Args = FHU::ParseArgumentsFromString(ArgString); - REQUIRE(Args.size() == 4); + REQUIRE(Args.size() == 2); CHECK(Args.at(0) == "Test"); - CHECK(Args.at(1) == "a"); - CHECK(Args.at(2) == "b"); - CHECK(Args.at(3) == "c"); + CHECK(Args.at(1) == "a b c"); } TEST_CASE("Basic - Bonus spaces between args") { const auto ArgString = "Test a b c"; auto Args = FHU::ParseArgumentsFromString(ArgString); - REQUIRE(Args.size() == 4); + REQUIRE(Args.size() == 2); CHECK(Args.at(0) == "Test"); - CHECK(Args.at(1) == "a"); - CHECK(Args.at(2) == "b"); - CHECK(Args.at(3) == "c"); + CHECK(Args.at(1) == "a b c"); } TEST_CASE("Basic - non printable") { const auto ArgString = "Test a b \x01c"; auto Args = FHU::ParseArgumentsFromString(ArgString); - REQUIRE(Args.size() == 4); + REQUIRE(Args.size() == 2); CHECK(Args.at(0) == "Test"); - CHECK(Args.at(1) == "a"); - CHECK(Args.at(2) == "b"); - CHECK(Args.at(3) == "\x01c"); + CHECK(Args.at(1) == "a b \x01c"); } TEST_CASE("Basic - Emoji") { const auto ArgString = "Test a b 🐸"; auto Args = FHU::ParseArgumentsFromString(ArgString); - REQUIRE(Args.size() == 4); + REQUIRE(Args.size() == 2); CHECK(Args.at(0) == "Test"); - CHECK(Args.at(1) == "a"); - CHECK(Args.at(2) == "b"); - CHECK(Args.at(3) == "🐸"); + CHECK(Args.at(1) == "a b 🐸"); } TEST_CASE("Basic - space at the end") { const auto ArgString = "Test a b 🐸 "; auto Args = FHU::ParseArgumentsFromString(ArgString); - REQUIRE(Args.size() == 4); + REQUIRE(Args.size() == 2); + CHECK(Args.at(0) == "Test"); + CHECK(Args.at(1) == "a b 🐸"); +} + +TEST_CASE("Basic - whitespace between parts") { + const auto ArgString = "\t\f\rTest\t\f\ra b 🐸\t\f\r"; + auto Args = FHU::ParseArgumentsFromString(ArgString); + REQUIRE(Args.size() == 2); CHECK(Args.at(0) == "Test"); - CHECK(Args.at(1) == "a"); - CHECK(Args.at(2) == "b"); - CHECK(Args.at(3) == "🐸"); + CHECK(Args.at(1) == "a b 🐸"); }