Skip to content

Commit dd2fbef

Browse files
authored
Move the VT parser's parse state into instanced storage (microsoft#3110)
The VT parser used to be keeping a boolean used to determine whether it was in bulk or single-character parse mode in a function-level static. That turned out to not be great. Fixes microsoft#3108; fixes microsoft#3073.
1 parent cd40faa commit dd2fbef

File tree

6 files changed

+134
-12
lines changed

6 files changed

+134
-12
lines changed

src/terminal/parser/stateMachine.cpp

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ StateMachine::StateMachine(IStateMachineEngine* const pEngine) :
2525
// rgusParams Initialized below
2626
_sOscNextChar(0),
2727
_sOscParam(0),
28-
_currRunLength(0)
28+
_currRunLength(0),
29+
_fProcessingIndividually(false)
2930
{
3031
ZeroMemory(_pwchOscStringBuffer, sizeof(_pwchOscStringBuffer));
3132
ZeroMemory(_rgusParams, sizeof(_rgusParams));
@@ -1346,20 +1347,16 @@ void StateMachine::ProcessString(const wchar_t* const rgwch, const size_t cch)
13461347
_pwchSequenceStart = rgwch;
13471348
_currRunLength = 0;
13481349

1349-
// This should be static, because if one string starts a sequence, and the next finishes it,
1350-
// we want the partial sequence state to persist.
1351-
static bool s_fProcessIndividually = false;
1352-
13531350
for (size_t cchCharsRemaining = cch; cchCharsRemaining > 0; cchCharsRemaining--)
13541351
{
1355-
if (s_fProcessIndividually)
1352+
if (_fProcessingIndividually)
13561353
{
13571354
// If we're processing characters individually, send it to the state machine.
13581355
ProcessCharacter(*_pwchCurr);
13591356
_pwchCurr++;
13601357
if (_state == VTStates::Ground) // Then check if we're back at ground. If we are, the next character (pwchCurr)
13611358
{ // is the start of the next run of characters that might be printable.
1362-
s_fProcessIndividually = false;
1359+
_fProcessingIndividually = false;
13631360
_pwchSequenceStart = _pwchCurr;
13641361
_currRunLength = 0;
13651362
}
@@ -1371,13 +1368,13 @@ void StateMachine::ProcessString(const wchar_t* const rgwch, const size_t cch)
13711368
FAIL_FAST_IF(!(_pwchSequenceStart + _currRunLength <= rgwch + cch));
13721369
_pEngine->ActionPrintString(_pwchSequenceStart, _currRunLength); // ... print all the chars leading up to it as part of the run...
13731370
_trace.DispatchPrintRunTrace(_pwchSequenceStart, _currRunLength);
1374-
s_fProcessIndividually = true; // begin processing future characters individually...
1371+
_fProcessingIndividually = true; // begin processing future characters individually...
13751372
_currRunLength = 0;
13761373
_pwchSequenceStart = _pwchCurr;
13771374
ProcessCharacter(*_pwchCurr); // ... Then process the character individually.
13781375
if (_state == VTStates::Ground) // If the character took us right back to ground, start another run after it.
13791376
{
1380-
s_fProcessIndividually = false;
1377+
_fProcessingIndividually = false;
13811378
_pwchSequenceStart = _pwchCurr + 1;
13821379
_currRunLength = 0;
13831380
}
@@ -1391,13 +1388,13 @@ void StateMachine::ProcessString(const wchar_t* const rgwch, const size_t cch)
13911388
}
13921389

13931390
// If we're at the end of the string and have remaining un-printed characters,
1394-
if (!s_fProcessIndividually && _currRunLength > 0)
1391+
if (!_fProcessingIndividually && _currRunLength > 0)
13951392
{
13961393
// print the rest of the characters in the string
13971394
_pEngine->ActionPrintString(_pwchSequenceStart, _currRunLength);
13981395
_trace.DispatchPrintRunTrace(_pwchSequenceStart, _currRunLength);
13991396
}
1400-
else if (s_fProcessIndividually)
1397+
else if (_fProcessingIndividually)
14011398
{
14021399
// One of the "weird things" in VT input is the case of something like
14031400
// <kbd>alt+[</kbd>. In VT, that's encoded as `\x1b[`. However, that's

src/terminal/parser/stateMachine.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,5 +150,9 @@ namespace Microsoft::Console::VirtualTerminal
150150
const wchar_t* _pwchCurr;
151151
const wchar_t* _pwchSequenceStart;
152152
size_t _currRunLength;
153+
154+
// This is tracked per state machine instance so that separate calls to Process*
155+
// can start and finish a sequence.
156+
bool _fProcessingIndividually;
153157
};
154158
}

src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
<ItemGroup>
1313
<ClCompile Include="InputEngineTest.cpp" />
1414
<ClCompile Include="OutputEngineTest.cpp" />
15+
<ClCompile Include="StateMachineTest.cpp" />
1516
<ClCompile Include="..\precomp.cpp">
1617
<PrecompiledHeader>Create</PrecompiledHeader>
1718
</ClCompile>

src/terminal/parser/ut_parser/Parser.UnitTests.vcxproj.filters

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@
1515
</Filter>
1616
</ItemGroup>
1717
<ItemGroup>
18-
<ClCompile Include="stateMachineTest.cpp">
18+
<ClCompile Include="InputEngineTest.cpp">
19+
<Filter>Source Files</Filter>
20+
</ClCompile>
21+
<ClCompile Include="OutputEngineTest.cpp">
22+
<Filter>Source Files</Filter>
23+
</ClCompile>
24+
<ClCompile Include="StateMachineTest.cpp">
1925
<Filter>Source Files</Filter>
2026
</ClCompile>
2127
<ClCompile Include="..\precomp.cpp">
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// Licensed under the MIT license.
3+
4+
#include "precomp.h"
5+
#include "WexTestClass.h"
6+
#include "../../inc/consoletaeftemplates.hpp"
7+
8+
#include "stateMachine.hpp"
9+
10+
using namespace WEX::Common;
11+
using namespace WEX::Logging;
12+
using namespace WEX::TestExecution;
13+
14+
namespace Microsoft
15+
{
16+
namespace Console
17+
{
18+
namespace VirtualTerminal
19+
{
20+
class StateMachineTest;
21+
class TestStateMachineEngine;
22+
};
23+
};
24+
};
25+
26+
using namespace Microsoft::Console::VirtualTerminal;
27+
28+
class Microsoft::Console::VirtualTerminal::TestStateMachineEngine : public IStateMachineEngine
29+
{
30+
public:
31+
bool ActionExecute(const wchar_t /* wch */) override { return true; };
32+
bool ActionExecuteFromEscape(const wchar_t /* wch */) override { return true; };
33+
bool ActionPrint(const wchar_t /* wch */) override { return true; };
34+
bool ActionPrintString(const wchar_t* const /* rgwch */,
35+
size_t const /* cch */) override { return true; };
36+
37+
bool ActionPassThroughString(const wchar_t* const /* rgwch */,
38+
size_t const /* cch */) override { return true; };
39+
40+
bool ActionEscDispatch(const wchar_t /* wch */,
41+
const unsigned short /* cIntermediate */,
42+
const wchar_t /* wchIntermediate */) override { return true; };
43+
44+
bool ActionClear() override { return true; };
45+
46+
bool ActionIgnore() override { return true; };
47+
48+
bool ActionOscDispatch(const wchar_t /* wch */,
49+
const unsigned short /* sOscParam */,
50+
wchar_t* const /* pwchOscStringBuffer */,
51+
const unsigned short /* cchOscString */) override { return true; };
52+
53+
bool ActionSs3Dispatch(const wchar_t /* wch */,
54+
const unsigned short* const /* rgusParams */,
55+
const unsigned short /* cParams */) override { return true; };
56+
57+
bool FlushAtEndOfString() const override { return false; };
58+
bool DispatchControlCharsFromEscape() const override { return false; };
59+
bool DispatchIntermediatesFromEscape() const override { return false; };
60+
61+
// ActionCsiDispatch is the only method that's actually implemented.
62+
bool ActionCsiDispatch(const wchar_t /* wch */,
63+
const unsigned short /* cIntermediate */,
64+
const wchar_t /* wchIntermediate */,
65+
_In_reads_(cParams) const unsigned short* const rgusParams,
66+
const unsigned short cParams) override
67+
{
68+
csiParams.emplace(rgusParams, rgusParams + cParams);
69+
return true;
70+
}
71+
72+
// This will only be populated if ActionCsiDispatch is called.
73+
std::optional<std::vector<unsigned short>> csiParams;
74+
};
75+
76+
class Microsoft::Console::VirtualTerminal::StateMachineTest
77+
{
78+
TEST_CLASS(StateMachineTest);
79+
80+
TEST_CLASS_SETUP(ClassSetup)
81+
{
82+
return true;
83+
}
84+
85+
TEST_CLASS_CLEANUP(ClassCleanup)
86+
{
87+
return true;
88+
}
89+
90+
TEST_METHOD(TwoStateMachinesDoNotInterfereWithEachother);
91+
};
92+
93+
void StateMachineTest::TwoStateMachinesDoNotInterfereWithEachother()
94+
{
95+
auto firstEnginePtr{ std::make_unique<TestStateMachineEngine>() };
96+
// this dance is required because StateMachine presumes to take ownership of its engine.
97+
const auto& firstEngine{ *firstEnginePtr.get() };
98+
StateMachine firstStateMachine{ firstEnginePtr.release() };
99+
100+
auto secondEnginePtr{ std::make_unique<TestStateMachineEngine>() };
101+
const auto& secondEngine{ *secondEnginePtr.get() };
102+
StateMachine secondStateMachine{ secondEnginePtr.release() };
103+
104+
firstStateMachine.ProcessString(L"\x1b[12"); // partial sequence
105+
secondStateMachine.ProcessString(L"\x1b[3C"); // full sequence on second parser
106+
firstStateMachine.ProcessString(L";34m"); // completion to previous partial sequence on first parser
107+
108+
std::vector<unsigned short> expectedFirstCsi{ 12u, 34u };
109+
std::vector<unsigned short> expectedSecondCsi{ 3u };
110+
111+
VERIFY_ARE_EQUAL(expectedFirstCsi, firstEngine.csiParams);
112+
VERIFY_ARE_EQUAL(expectedSecondCsi, secondEngine.csiParams);
113+
}

src/terminal/parser/ut_parser/sources

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ SOURCES = \
2323
$(SOURCES) \
2424
OutputEngineTest.cpp \
2525
InputEngineTest.cpp \
26+
StateMachineTest.cpp \
2627

2728
# The InputEngineTest requires VTRedirMapVirtualKeyW, which means we need the
2829
# ServiceLocator, which means we need the entire host and all it's dependencies,

0 commit comments

Comments
 (0)