@samitouri / QOSAMI-WSL / commits / fae04339

Adjust global argument freezing (#41305)

David Bennett committed Aug 10, 2026 at 17:52 UTC fae0433930016b01f57c892d076583e37dff53fc
7 files changed +55 -14
src/windows/wslc/arguments/ArgMap.h
+11 -1
@@ -117,6 +117,11 @@ public:
117 return m_validated.count(type);
118 }
119
120 + bool IsValidated(ArgType type) const
121 + {
122 + return m_validatedTypes.count(type) != 0;
123 + }
124 +
125 // Drops `type`'s memoized validation state (converted cache and validated record) so it never
126 // outlives the raw data.
127 void InvalidateValidated(ArgType type)
@@ -128,6 +133,11 @@ public:
133 // Records `type` as validated for its current raw values so reads skip re-validation.
134 void MarkValidated(ArgType type)
135 {
136 + if (IsValidated(type))
137 + {
138 + return;
139 + }
140 +
141 ThrowIfImmutable(type, "mark the argument as validated");
142 m_validatedTypes.insert(type);
143 }
@@ -231,7 +241,7 @@ private:
241 // demand, and its errors reported, exactly like a command-line value.
242 void EnsureValidated(ArgType type)
243 {
234 - if (m_validatedTypes.count(type) != 0)
244 + if (IsValidated(type))
245 {
246 return;
247 }
src/windows/wslc/arguments/ArgumentValidation.cpp
+5
@@ -72,6 +72,11 @@ namespace {
72 // validated on success.
73 void Argument::Validate(ArgMap& execArgs) const
74 {
75 + if (execArgs.IsValidated(m_argType))
76 + {
77 + return;
78 + }
79 +
80 switch (m_argType)
81 {
82 case ArgType::BuildLabel:
src/windows/wslc/core/CLIExecutionContext.cpp
+4 -6
@@ -16,13 +16,11 @@ HANDLE CLIExecutionContext::CreateCancelEvent()
16 return CancelEvent.get();
17 }
18
19 -// This method should be idempotent.
20 -void CLIExecutionContext::ApplyGlobalOptions()
19 +void CLIExecutionContext::ApplyGlobalEnvironmentOptions()
20 {
22 - if (GlobalArgs.GetValue<ArgType::NoColor>())
23 - {
24 - Terminal.SetNoColor(true);
25 - }
21 + // NoColor is environment-only and resolved before any output. Freezing it keeps the terminal
22 + // color state consistent for the entire invocation.
23 + Terminal.SetNoColor(GlobalArgs.GetValue<ArgType::NoColor>());
24 }
25
26 } // namespace wsl::windows::wslc::execution
src/windows/wslc/core/CLIExecutionContext.h
+2 -3
@@ -50,9 +50,8 @@ struct CLIExecutionContext : public wsl::windows::common::ExecutionContext
50
51 HANDLE CreateCancelEvent();
52
53 - // Single chokepoint that turns parsed GlobalArgs into process-wide effects
54 - // (debug logging, VT color, ...). Idempotent.
55 - void ApplyGlobalOptions();
53 + // Applies and freezes environment-only global options before command-line parsing reports errors.
54 + void ApplyGlobalEnvironmentOptions();
55 };
56
57 } // namespace wsl::windows::wslc::execution
src/windows/wslc/core/Main.cpp
+2 -3
@@ -92,7 +92,7 @@ try
92 // throw can't reroute through the colored-help error path.
93 auto envDefs = command->GetGlobalsAndEnvArguments();
94 ApplyEnvironmentOptions(context.GlobalArgs, envDefs);
95 - context.ApplyGlobalOptions();
95 + context.ApplyGlobalEnvironmentOptions();
96
97 // Past this point, environment variable options are in effect.
98
@@ -119,9 +119,8 @@ try
119 /*stopOnUnknown*/ true,
120 /*overridableDefaults*/ envDefs);
121 command->ValidateArguments(context.GlobalArgs, envDefs, /*runInternalHook*/ false);
122 - context.ApplyGlobalOptions();
122
124 - // Past this point, global options are in effect.
123 + // Past this point, global option parsing and validation are complete.
124
125 // Pass 2 - Subcommand and leaf command resolution.
126 std::unique_ptr<Command> subCommand = command->FindSubCommand(invocation);
test/windows/wslc/WSLCCLIArgumentUnitTests.cpp
+5 -1
@@ -627,6 +627,11 @@ class WSLCCLIArgumentUnitTests
627 ArgMap args;
628 args.Add(ArgType::Signal, std::wstring(L"SIGTERM"));
629 VERIFY_ARE_EQUAL(args.GetValue<ArgType::Signal>(), WSLCSignalSIGTERM);
630 + VERIFY_ARE_EQUAL(args.CountValidated(ArgType::Signal), static_cast<size_t>(1));
631 +
632 + Argument::Create(ArgType::Signal).Validate(args);
633 + args.MarkValidated(ArgType::Signal);
634 + VERIFY_ARE_EQUAL(args.CountValidated(ArgType::Signal), static_cast<size_t>(1));
635
636 const auto verifyImmutableFailure = [](const auto& operation) {
637 VERIFY_THROWS_SPECIFIC(operation(), wil::ResultException, [](const wil::ResultException& e) {
@@ -638,7 +643,6 @@ class WSLCCLIArgumentUnitTests
643 verifyImmutableFailure([&] { args.Remove(ArgType::Signal); });
644 verifyImmutableFailure([&] { args.InvalidateValidated(ArgType::Signal); });
645 verifyImmutableFailure([&] { args.AddValidated<ArgType::Signal>(WSLCSignalSIGKILL); });
641 - verifyImmutableFailure([&] { args.MarkValidated(ArgType::Signal); });
646
647 // Immutability is per argument; other arguments remain writable until they are read.
648 args.Add(ArgType::StopTimeout, std::wstring(L"30"));
test/windows/wslc/WSLCCLIExecutionUnitTests.cpp
+26
@@ -53,6 +53,32 @@ class WSLCCLIExecutionUnitTests
53 return true;
54 }
55
56 + TEST_METHOD(GlobalEnvironmentOptions_NoColorIsAppliedAndFrozen)
57 + {
58 + {
59 + CLIExecutionContext context;
60 +
61 + context.ApplyGlobalEnvironmentOptions();
62 + VERIFY_IS_FALSE(context.Terminal.IsNoColor());
63 +
64 + VERIFY_THROWS_SPECIFIC(context.GlobalArgs.Add<ArgType::NoColor>(true), wil::ResultException, [](const wil::ResultException& e) {
65 + return e.GetErrorCode() == E_ILLEGAL_METHOD_CALL;
66 + });
67 + }
68 +
69 + {
70 + CLIExecutionContext present;
71 + present.GlobalArgs.Add<ArgType::NoColor>(true);
72 + present.ApplyGlobalEnvironmentOptions();
73 + VERIFY_IS_TRUE(present.Terminal.IsNoColor());
74 +
75 + VERIFY_NO_THROW(Argument::Create(ArgType::NoColor).Validate(present.GlobalArgs));
76 + VERIFY_THROWS_SPECIFIC(present.GlobalArgs.Remove(ArgType::NoColor), wil::ResultException, [](const wil::ResultException& e) {
77 + return e.GetErrorCode() == E_ILLEGAL_METHOD_CALL;
78 + });
79 + }
80 + }
81 +
82 // Test: Verify EnumVariantMap on DataMap for Context Data
83 TEST_METHOD(EnumVariantMap_DataMapValidation)
84 {