CLI: Fix parser treating stdin positional ('-') as an argument specifier error. (#40527)
David Bennett committed
May 13, 2026 at 23:19 UTC
7c3394fe67f3e6f3bcd4cee09fd31287cc711e69
4 files changed
+39
-26
src/windows/wslc/arguments/ArgumentParser.cpp
+24
-22
@@ -67,21 +67,25 @@ void ParseArgumentsStateMachine::ThrowIfError() const
67
}
68
}
69
70
-const Argument* ParseArgumentsStateMachine::NextPositional()
70
+void ParseArgumentsStateMachine::AdvanceToNextPositional(std::vector<Argument>::iterator& itr) const
71
{
72
- // Find the next appropriate positional arg if the current itr isn't one or has hit its limit.
73
- while (m_positionalSearchItr != m_positionalArgs.end() &&
74
- (m_executionArgs.Count(m_positionalSearchItr->Type()) == m_positionalSearchItr->Limit()))
72
+ while (itr != m_positionalArgs.end() && (m_executionArgs.Count(itr->Type()) == itr->Limit()))
73
{
76
- ++m_positionalSearchItr;
74
+ ++itr;
75
}
76
+}
77
79
- if (m_positionalSearchItr == m_positionalArgs.end())
80
- {
81
- return nullptr;
82
- }
78
+const Argument* ParseArgumentsStateMachine::NextPositional()
79
+{
80
+ AdvanceToNextPositional(m_positionalSearchItr);
81
+ return m_positionalSearchItr != m_positionalArgs.end() ? &*m_positionalSearchItr : nullptr;
82
+}
83
84
- return &*m_positionalSearchItr;
84
+bool ParseArgumentsStateMachine::HasNextPositional() const
85
+{
86
+ auto itr = m_positionalSearchItr;
87
+ AdvanceToNextPositional(itr);
88
+ return itr != m_positionalArgs.end();
89
}
90
91
// Parse arguments as such:
@@ -127,7 +131,14 @@ ParseArgumentsStateMachine::State ParseArgumentsStateMachine::StepInternal()
131
// The currentArg is non-empty, and starts with a -.
132
if (currArg.length() == 1)
133
{
130
- // If it is only one character, then it is an error since it is neither an alias nor a named argument.
134
+ if (HasNextPositional())
135
+ {
136
+ // The '-' character may be a valid positional argument value (ex: stdin), so treat this
137
+ // as a positional argument if there are any positionals left to fill.
138
+ return ProcessPositionalArgument(currArg);
139
+ }
140
+
141
+ // No positional argument remaining means this is an invalid argument.
142
return ArgumentException(Localization::WSLCCLI_InvalidArgumentSpecifierError(currArg));
143
}
144
@@ -141,10 +152,10 @@ ParseArgumentsStateMachine::State ParseArgumentsStateMachine::StepInternal()
152
return ProcessNamedArgument(currArg);
153
}
154
144
-// Assumes non-empty and does not begin with '-'.
155
+// Assumes non-empty.
156
ParseArgumentsStateMachine::State ParseArgumentsStateMachine::ProcessPositionalArgument(const std::wstring_view& currArg)
157
{
147
- WI_ASSERT(!currArg.empty() && currArg[0] != WSLC_CLI_ARG_ID_CHAR);
158
+ WI_ASSERT(!currArg.empty());
159
160
const Argument* nextPositional = NextPositional();
161
if (!nextPositional)
@@ -173,15 +184,6 @@ ParseArgumentsStateMachine::State ParseArgumentsStateMachine::ProcessAnchoredPos
184
if ((m_executionArgs.Count(m_anchorPositional.value().Type()) < m_anchorPositional.value().Limit()) ||
185
(m_anchorPositional.value().Limit() == NO_LIMIT))
186
{
176
- // Validate that we don't have any invalid argument specifiers.
177
- // Anchor positionals with multiple values should be order-independent, which means a
178
- // '-' at the start of the first one would be invalid, so it should also be invalid for
179
- // all other anchor positionals of the same type.
180
- if (!currArg.empty() && currArg[0] == WSLC_CLI_ARG_ID_CHAR)
181
- {
182
- return ArgumentException(Localization::WSLCCLI_InvalidArgumentSpecifierError(currArg));
183
- }
184
-
187
m_executionArgs.Add(m_anchorPositional.value().Type(), std::wstring{currArg});
188
return {};
189
}
src/windows/wslc/arguments/ArgumentParser.h
+6
@@ -89,6 +89,9 @@ struct ParseArgumentsStateMachine
89
// Gets the next positional argument, or nullptr if there is not one.
90
const Argument* NextPositional();
91
92
+ // Returns true if there is a next positional argument available, without advancing the iterator.
93
+ bool HasNextPositional() const;
94
+
95
const std::vector<Argument>& Arguments() const
96
{
97
return m_arguments;
@@ -102,6 +105,9 @@ private:
105
State ProcessNamedArgument(const std::wstring_view& currArg);
106
void ProcessAdjoinedValue(ArgType type, std::wstring_view value);
107
108
+ // Advances the given iterator past any positionals that have reached their limit.
109
+ void AdvanceToNextPositional(std::vector<Argument>::iterator& itr) const;
110
+
111
Invocation& m_invocation;
112
ArgMap& m_executionArgs;
113
std::vector<Argument> m_arguments;
test/windows/wslc/ParserTestCases.h
+7
-1
@@ -121,6 +121,12 @@ WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc image1 \\command\\?"" --f -z forward h
121
WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc jrottenberg/ffmpeg:4.4-alpine ffmpeg -i http://url/to/media.mp4 -stats)") \
122
WSLC_PARSER_TEST_CASE(Run, true, L"wslc jrottenberg/ffmpeg:4.4-alpine \\\nffmpeg \\\n-i http://url/to/media.mp4 \\\n-stats") \
123
\
124
+/* Stdin dash ('-') as a positional value. A lone '-' conventionally means stdin and must \
125
+ * be accepted as a valid positional rather than treated as an invalid flag specifier. */ \
126
+WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc -)") \
127
+WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc - command)") \
128
+WSLC_PARSER_TEST_CASE(Run, true, LR"(wslc --verbose -)") \
129
+\
130
/* List cases with multiple args and flags that can come after the optional multi-positional. */ \
131
WSLC_PARSER_TEST_CASE(List, true, LR"(wslc)") \
132
WSLC_PARSER_TEST_CASE(List, true, LR"(wslc cont1)") \
@@ -138,4 +144,4 @@ WSLC_PARSER_TEST_CASE(List, false, LR"(wslc -vp cont1)") \
144
WSLC_PARSER_TEST_CASE(List, false, LR"(wslc cont1 -v cont2 -12)") \
145
WSLC_PARSER_TEST_CASE(List, false, LR"(wslc cont1 --verbose=false cont2)") \
146
WSLC_PARSER_TEST_CASE(List, false, LR"(wslc cont1 cont2 --invalidarg)")
141
-// clang-format on
\ No newline at end of file
147
+// clang-format on
test/windows/wslc/WSLCCLIParserUnitTests.cpp
+2
-3
@@ -46,8 +46,7 @@ class WSLCCLIParserUnitTests
46
return true;
47
}
48
49
- // Test: Verify command line to argv mapping and GetRemainingRawCommandLineFromIndex
50
- TEST_METHOD(ParserTest_StateMachine_PositionalForward)
49
+ TEST_METHOD(ParserTest_ParserCases)
50
{
51
// Build test cases from x-macro
52
std::vector<ParserTestCase> testCases = {
@@ -173,4 +172,4 @@ class WSLCCLIParserUnitTests
172
}
173
}
174
};
176
-} // namespace WSLCCLIParserUnitTests
\ No newline at end of file
175
+} // namespace WSLCCLIParserUnitTests