Fix issue with svccommio incorrectly resetting console state in the move operator (#14083)
The ConsoleInput and ConsoleOutput classes used = default move operations, which caused the moved-from temporary's destructor to restore the original console mode (re-enabling ENABLE_PROCESSED_INPUT) after the object was moved into the std::optional. The fix uses emplace() to construct objects directly in-place, avoiding moves entirely. Move operations are now deleted to prevent future misuse. Co-authored-by: Ben Hillis <benhill@ntdev.microsoft.com>
Ben Hillis committed
Jan 20, 2026 at 13:27 UTC
a03462f50b5c609c13e5293207abf41e560d2e96
2 files changed
+30
-53
src/windows/common/svccommio.cpp
+19
-38
@@ -72,22 +72,9 @@ CATCH_LOG()
72
73
namespace wsl::windows::common {
74
75
-std::optional<ConsoleInput> ConsoleInput::Create(HANDLE Handle)
75
+ConsoleInput::ConsoleInput(HANDLE Handle, DWORD SavedMode) :
76
+ m_Handle(Handle), m_SavedMode(SavedMode), m_SavedCodePage(GetConsoleCP())
77
{
77
- DWORD Mode;
78
- if (GetFileType(Handle) == FILE_TYPE_CHAR && GetConsoleMode(Handle, &Mode))
79
- {
80
- return ConsoleInput(Handle, Mode);
81
- }
82
-
83
- return std::nullopt;
84
-}
85
-
86
-ConsoleInput::ConsoleInput(HANDLE Handle, DWORD SavedMode) : m_Handle(Handle), m_SavedMode(SavedMode)
87
-{
88
- // Save code page.
89
- m_SavedCodePage = GetConsoleCP();
90
-
78
// Configure for raw input with VT support.
79
DWORD NewMode = m_SavedMode;
80
WI_SetAllFlags(NewMode, ENABLE_WINDOW_INPUT | ENABLE_VIRTUAL_TERMINAL_INPUT);
@@ -104,29 +91,9 @@ ConsoleInput::~ConsoleInput()
91
LOG_IF_WIN32_BOOL_FALSE(SetConsoleCP(m_SavedCodePage));
92
}
93
107
-std::optional<ConsoleOutput> ConsoleOutput::Create()
108
-{
109
- wil::unique_hfile ConsoleHandle(
110
- CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr));
111
-
112
- if (ConsoleHandle)
113
- {
114
- DWORD Mode;
115
- if (GetConsoleMode(ConsoleHandle.get(), &Mode))
116
- {
117
- return ConsoleOutput(std::move(ConsoleHandle), Mode);
118
- }
119
- }
120
-
121
- return std::nullopt;
122
-}
123
-
94
ConsoleOutput::ConsoleOutput(wil::unique_hfile&& ConsoleHandle, DWORD SavedMode) :
125
- m_ConsoleHandle(std::move(ConsoleHandle)), m_SavedMode(SavedMode)
95
+ m_ConsoleHandle(std::move(ConsoleHandle)), m_SavedMode(SavedMode), m_SavedCodePage(GetConsoleOutputCP())
96
{
127
- // Save code page.
128
- m_SavedCodePage = GetConsoleOutputCP();
129
-
97
// Configure for VT output.
98
DWORD NewMode = m_SavedMode;
99
WI_SetAllFlags(NewMode, ENABLE_PROCESSED_OUTPUT | ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN);
@@ -149,10 +116,24 @@ SvcCommIo::SvcCommIo()
116
const HANDLE ErrorHandle = GetStdHandle(STD_ERROR_HANDLE);
117
118
// Configure input console
152
- m_ConsoleInput = ConsoleInput::Create(InputHandle);
119
+ DWORD InputMode;
120
+ if (GetFileType(InputHandle) == FILE_TYPE_CHAR && GetConsoleMode(InputHandle, &InputMode))
121
+ {
122
+ m_ConsoleInput.emplace(InputHandle, InputMode);
123
+ }
124
125
// Configure output console
155
- m_ConsoleOutput = ConsoleOutput::Create();
126
+ wil::unique_hfile ConsoleHandle(
127
+ CreateFileW(L"CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, 0, nullptr));
128
+
129
+ if (ConsoleHandle)
130
+ {
131
+ DWORD OutputMode;
132
+ if (GetConsoleMode(ConsoleHandle.get(), &OutputMode))
133
+ {
134
+ m_ConsoleOutput.emplace(std::move(ConsoleHandle), OutputMode);
135
+ }
136
+ }
137
138
// Initialize the standard handles structure
139
const bool IsConsoleInput = m_ConsoleInput.has_value();
src/windows/common/svccommio.hpp
+11
-15
@@ -25,38 +25,34 @@ namespace wsl::windows::common {
25
class ConsoleInput
26
{
27
public:
28
- static std::optional<ConsoleInput> Create(HANDLE Handle);
28
+ ConsoleInput(HANDLE Handle, DWORD SavedMode);
29
~ConsoleInput();
30
ConsoleInput(const ConsoleInput&) = delete;
31
ConsoleInput& operator=(const ConsoleInput&) = delete;
32
- ConsoleInput(ConsoleInput&&) = default;
33
- ConsoleInput& operator=(ConsoleInput&&) = default;
32
+ ConsoleInput(ConsoleInput&&) = delete;
33
+ ConsoleInput& operator=(ConsoleInput&&) = delete;
34
35
private:
36
- ConsoleInput(HANDLE Handle, DWORD SavedMode);
37
-
38
- HANDLE m_Handle = nullptr;
39
- DWORD m_SavedMode = 0;
40
- UINT m_SavedCodePage = 0;
36
+ HANDLE m_Handle{};
37
+ DWORD m_SavedMode{};
38
+ UINT m_SavedCodePage{};
39
};
40
41
// RAII wrapper for console output configuration and restoration
42
class ConsoleOutput
43
{
44
public:
47
- static std::optional<ConsoleOutput> Create();
45
+ ConsoleOutput(wil::unique_hfile&& ConsoleHandle, DWORD SavedMode);
46
~ConsoleOutput();
47
ConsoleOutput(const ConsoleOutput&) = delete;
48
ConsoleOutput& operator=(const ConsoleOutput&) = delete;
51
- ConsoleOutput(ConsoleOutput&&) = default;
52
- ConsoleOutput& operator=(ConsoleOutput&&) = default;
49
+ ConsoleOutput(ConsoleOutput&&) = delete;
50
+ ConsoleOutput& operator=(ConsoleOutput&&) = delete;
51
52
private:
55
- ConsoleOutput(wil::unique_hfile&& ConsoleHandle, DWORD SavedMode);
56
-
53
wil::unique_hfile m_ConsoleHandle;
58
- DWORD m_SavedMode = 0;
59
- UINT m_SavedCodePage = 0;
54
+ DWORD m_SavedMode{};
55
+ UINT m_SavedCodePage{};
56
};
57
58
class SvcCommIo