diff --git a/.github/actions/spelling/expect/expect.txt b/.github/actions/spelling/expect/expect.txt index 3be64694a0..cc86eb8723 100644 --- a/.github/actions/spelling/expect/expect.txt +++ b/.github/actions/spelling/expect/expect.txt @@ -193,7 +193,6 @@ chh chk CHT Cic -CLA Clcompile CLE cleartype @@ -480,7 +479,6 @@ defterm DELAYLOAD DELETEONRELEASE Delt -demoable depersist deprioritized deserializers @@ -537,7 +535,6 @@ DSSCL DSwap DTest DTTERM -DUMMYUNIONNAME dup'ed dvi dwl @@ -590,7 +587,6 @@ ETW EUDC EVENTID eventing -everytime evflags evt execd @@ -802,7 +798,6 @@ HIBYTE hicon HIDEWINDOW hinst -Hirots HISTORYBUFS HISTORYNODUP HISTORYSIZE @@ -903,7 +898,6 @@ INSERTMODE INTERACTIVITYBASE INTERCEPTCOPYPASTE INTERNALNAME -inthread intsafe INVALIDARG INVALIDATERECT @@ -1260,14 +1254,12 @@ ntm nto ntrtl ntstatus -ntsubauth NTSYSCALLAPI nttree nturtl ntuser NTVDM ntverp -NTWIN nugetversions nullability nullness @@ -1306,8 +1298,6 @@ opencode opencon openconsole openconsoleproxy -OPENIF -OPENLINK openps openvt ORIGINALFILENAME @@ -1360,9 +1350,7 @@ pcg pch PCIDLIST PCIS -PCLIENT PCLONG -PCOBJECT pcon PCONSOLE PCONSOLEENDTASK @@ -1374,7 +1362,6 @@ pcshell PCSHORT PCSR PCSTR -PCUNICODE PCWCH PCWCHAR PCWSTR @@ -1423,7 +1410,6 @@ PLOGICAL pnm PNMLINK pntm -PNTSTATUS POBJECT Podcast POINTSLIST @@ -1442,7 +1428,6 @@ ppf ppguid ppidl PPROC -PPROCESS ppropvar ppsi ppsl @@ -1506,7 +1491,6 @@ ptrs ptsz PTYIn PUCHAR -PUNICODE pwch PWDDMCONSOLECONTEXT pws @@ -1568,7 +1552,6 @@ REGISTEROS REGISTERVDM regkey REGSTR -reingest RELBINPATH remoting renamer @@ -1863,6 +1846,7 @@ TDP TEAMPROJECT tearoff Teb +Techo tellp teraflop terminalcore @@ -2005,7 +1989,6 @@ unittesting unittests unk unknwn -unmark UNORM unparseable unregistering diff --git a/src/host/inputReadHandleData.cpp b/src/host/inputReadHandleData.cpp index e8ab613cb1..8628d13113 100644 --- a/src/host/inputReadHandleData.cpp +++ b/src/host/inputReadHandleData.cpp @@ -20,7 +20,7 @@ bool INPUT_READ_HANDLE_DATA::IsInputPending() const bool INPUT_READ_HANDLE_DATA::IsMultilineInput() const { - FAIL_FAST_IF(!_isInputPending); // we shouldn't have multiline input without a pending input. + assert(_isInputPending); // we shouldn't have multiline input without a pending input. return _isMultilineInput; } diff --git a/src/host/readDataCooked.cpp b/src/host/readDataCooked.cpp index 15f6e49d31..6ea80796f7 100644 --- a/src/host/readDataCooked.cpp +++ b/src/host/readDataCooked.cpp @@ -1019,27 +1019,43 @@ void COOKED_READ_DATA::SavePendingInput(const size_t index, const bool multiline } Tracing::s_TraceCookedRead(_clientProcess, _backupLimit, base::saturated_cast(idx)); - ProcessAliases(LineCount); + // Don't be fooled by ProcessAliases only taking one argument. It rewrites multiple + // class members on return, including `_bytesRead`, requiring us to reconstruct `input`. + ProcessAliases(LineCount); + input = { _backupLimit, _bytesRead / sizeof(wchar_t) }; + + // The exact reasons for this are unclear to me (the one writing this comment), but this code used to + // split the contents of a multiline alias (for instance `doskey test=echo foo$Techo bar$Techo baz`) + // into multiple separate read outputs, ensuring that the client receives them line by line. + // + // This code first truncates the `input` to only contain the first line, so that Consume() below only + // writes that line into the user buffer. We'll later store the remainder in SaveMultilinePendingInput(). if (LineCount > 1) { - input = input.substr(0, idx + 1); + // ProcessAliases() is supposed to end each line with \r\n. If it doesn't we might as well fail-fast. + const auto firstLineEnd = input.find(UNICODE_LINEFEED) + 1; + input = input.substr(0, std::min(input.size(), firstLineEnd)); } } } + const auto inputSizeBefore = input.size(); GetInputBuffer()->Consume(isUnicode, input, writer); - if (!input.empty()) + if (LineCount > 1) { - if (LineCount > 1) - { - GetInputReadHandleData()->SaveMultilinePendingInput(input); - } - else - { - GetInputReadHandleData()->SavePendingInput(input); - } + // This is a continuation of the above identical if condition. + // We've truncated the `input` slice and now we need to restore it. + const auto inputSizeAfter = input.size(); + const auto amountConsumed = inputSizeBefore - inputSizeAfter; + input = { _backupLimit, _bytesRead / sizeof(wchar_t) }; + input = input.substr(std::min(input.size(), amountConsumed)); + GetInputReadHandleData()->SaveMultilinePendingInput(input); + } + else if (!input.empty()) + { + GetInputReadHandleData()->SavePendingInput(input); } numBytes = _userBufferSize - writer.size(); diff --git a/src/host/stream.cpp b/src/host/stream.cpp index 92c76b89ad..5ab812bda9 100644 --- a/src/host/stream.cpp +++ b/src/host/stream.cpp @@ -288,28 +288,36 @@ try { bytesRead = 0; - auto pending = readHandleState.GetPendingInput(); + const auto pending = readHandleState.GetPendingInput(); + auto input = pending; + // This is basically the continuation of COOKED_READ_DATA::_handlePostCharInputLoop. if (readHandleState.IsMultilineInput()) { - const auto idx = pending.find(UNICODE_LINEFEED); - if (idx != decltype(pending)::npos) - { - // +1 to include the newline. - pending = pending.substr(0, idx + 1); - } + const auto firstLineEnd = input.find(UNICODE_LINEFEED) + 1; + input = input.substr(0, std::min(input.size(), firstLineEnd)); } + const auto inputSizeBefore = input.size(); std::span writer{ buffer }; - inputBuffer.Consume(unicode, pending, writer); + inputBuffer.Consume(unicode, input, writer); - if (pending.empty()) + // Since we truncated `input` to only include the first line, + // we need to restore `input` here to the entirety of the remaining input. + if (readHandleState.IsMultilineInput()) + { + const auto inputSizeAfter = input.size(); + const auto amountConsumed = inputSizeBefore - inputSizeAfter; + input = pending.substr(std::min(pending.size(), amountConsumed)); + } + + if (input.empty()) { readHandleState.CompletePending(); } else { - readHandleState.UpdatePending(pending); + readHandleState.UpdatePending(input); } bytesRead = buffer.size() - writer.size();