From 1be024ee450f2d3cb07086f6141d50f291c1910b Mon Sep 17 00:00:00 2001 From: Simon Tatham Date: Tue, 3 May 2022 10:33:11 +0100 Subject: [PATCH] [Windows] Fix cmd line tokenization of unclosed quotes. When cl::TokenizeWindowsCommandLine received a command line with an unterminated double-quoted string at the end, it would discard the text within that string. That doesn't match the behavior of the standard Windows C library, which will return the text in the unclosed quoted string as an argv word. Fixed, and added extra unit tests in that area. In some cases (specifically the one in Bugzilla #47579) this could cause TokenizeWindowsCommandLine to return a zero-length list of arguments, leading to an array overrun at the call site in windows::GetCommandLineArguments. Added a check there, for extra safety: now windows::GetCommandLineArguments will return an error code instead of failing an assertion. (This change was written as part of https://reviews.llvm.org/D122914, but split into a separate commit at the last minute at the code reviewer's suggestion, because it's fixing an unrelated bug in the same area. The rest of D122914 will follow in the next commit.) --- llvm/lib/Support/CommandLine.cpp | 2 +- llvm/lib/Support/Windows/Process.inc | 3 +++ llvm/unittests/Support/CommandLineTest.cpp | 14 +++++++++++++- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/llvm/lib/Support/CommandLine.cpp b/llvm/lib/Support/CommandLine.cpp index 4c9250236a3b..2f749bf7a698 100644 --- a/llvm/lib/Support/CommandLine.cpp +++ b/llvm/lib/Support/CommandLine.cpp @@ -1008,7 +1008,7 @@ tokenizeWindowsCommandLineImpl(StringRef Src, StringSaver &Saver, } } - if (State == UNQUOTED) + if (State != INIT) AddToken(Saver.save(Token.str())); } diff --git a/llvm/lib/Support/Windows/Process.inc b/llvm/lib/Support/Windows/Process.inc index dfaab1613de1..b1af298d9e83 100644 --- a/llvm/lib/Support/Windows/Process.inc +++ b/llvm/lib/Support/Windows/Process.inc @@ -255,6 +255,9 @@ windows::GetCommandLineArguments(SmallVectorImpl &Args, return EC; } + if (Args.size() == 0) + return std::make_error_code(std::errc::invalid_argument); + SmallVector Arg0(Args[0], Args[0] + strlen(Args[0])); SmallVector Filename; sys::path::remove_filename(Arg0); diff --git a/llvm/unittests/Support/CommandLineTest.cpp b/llvm/unittests/Support/CommandLineTest.cpp index dd02d9201265..7f751e5e101b 100644 --- a/llvm/unittests/Support/CommandLineTest.cpp +++ b/llvm/unittests/Support/CommandLineTest.cpp @@ -237,12 +237,24 @@ TEST(CommandLineTest, TokenizeWindowsCommandLine2) { } TEST(CommandLineTest, TokenizeWindowsCommandLineQuotedLastArgument) { + // Whitespace at the end of the command line doesn't cause an empty last word + const char Input0[] = R"(a b c d )"; + const char *const Output0[] = {"a", "b", "c", "d"}; + testCommandLineTokenizer(cl::TokenizeWindowsCommandLine, Input0, Output0); + + // But an explicit "" does const char Input1[] = R"(a b c d "")"; const char *const Output1[] = {"a", "b", "c", "d", ""}; testCommandLineTokenizer(cl::TokenizeWindowsCommandLine, Input1, Output1); + + // An unterminated quoted string is also emitted as an argument word, empty + // or not const char Input2[] = R"(a b c d ")"; - const char *const Output2[] = {"a", "b", "c", "d"}; + const char *const Output2[] = {"a", "b", "c", "d", ""}; testCommandLineTokenizer(cl::TokenizeWindowsCommandLine, Input2, Output2); + const char Input3[] = R"(a b c d "text)"; + const char *const Output3[] = {"a", "b", "c", "d", "text"}; + testCommandLineTokenizer(cl::TokenizeWindowsCommandLine, Input3, Output3); } TEST(CommandLineTest, TokenizeAndMarkEOLs) {