Skip to content

Commit baba406

Browse files
authored
Fix a crash when closing panes (#17333)
Calling Close() from within WalkPanes is not safe. Simply using _FindPane is enough to fix this. This PR also fixes another potential source of infinite recursion, and fixes panes being passed by-value into the callbacks. Closes #17305 ## Validation Steps Performed * Split panes vertically 3 times * `exit` the middle, the bottom and final one, in that order * Doesn't crash ✅
1 parent bdc7c4f commit baba406

File tree

5 files changed

+37
-32
lines changed

5 files changed

+37
-32
lines changed

src/cascadia/TerminalApp/Pane.cpp

+16-5
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ std::shared_ptr<Pane> Pane::NextPane(const std::shared_ptr<Pane> targetPane)
467467
std::shared_ptr<Pane> nextPane = nullptr;
468468
auto foundTarget = false;
469469

470-
auto foundNext = WalkTree([&](auto pane) {
470+
auto foundNext = WalkTree([&](const auto& pane) {
471471
// If we are a parent pane we don't want to move to one of our children
472472
if (foundTarget && targetPane->_HasChild(pane))
473473
{
@@ -985,6 +985,17 @@ void Pane::_ContentLostFocusHandler(const winrt::Windows::Foundation::IInspectab
985985
// - <none>
986986
void Pane::Close()
987987
{
988+
// Pane has two events, CloseRequested and Closed. CloseRequested is raised by the content asking to be closed,
989+
// but also by the window who owns the tab when it's closing. The event is then caught by the TerminalTab which
990+
// calls Close() which then raises the Closed event. Now, if this is the last pane in the window, this will result
991+
// in the window raising CloseRequested again which leads to infinite recursion, so we need to guard against that.
992+
// Ideally we would have just a single event in the future.
993+
if (_closed)
994+
{
995+
return;
996+
}
997+
998+
_closed = true;
988999
// Fire our Closed event to tell our parent that we should be removed.
9891000
Closed.raise(nullptr, nullptr);
9901001
}
@@ -1341,7 +1352,7 @@ std::shared_ptr<Pane> Pane::DetachPane(std::shared_ptr<Pane> pane)
13411352
detached->_ApplySplitDefinitions();
13421353

13431354
// Trigger the detached event on each child
1344-
detached->WalkTree([](auto pane) {
1355+
detached->WalkTree([](const auto& pane) {
13451356
pane->Detached.raise(pane);
13461357
});
13471358

@@ -1542,7 +1553,7 @@ void Pane::_CloseChild(const bool closeFirst)
15421553
{
15431554
// update our path to our first remaining leaf
15441555
_parentChildPath = _firstChild;
1545-
_firstChild->WalkTree([](auto p) {
1556+
_firstChild->WalkTree([](const auto& p) {
15461557
if (p->_IsLeaf())
15471558
{
15481559
return true;
@@ -2398,7 +2409,7 @@ void Pane::Id(uint32_t id) noexcept
23982409
bool Pane::FocusPane(const uint32_t id)
23992410
{
24002411
// Always clear the parent child path if we are focusing a leaf
2401-
return WalkTree([=](auto p) {
2412+
return WalkTree([=](const auto& p) {
24022413
p->_parentChildPath.reset();
24032414
if (p->_id == id)
24042415
{
@@ -2421,7 +2432,7 @@ bool Pane::FocusPane(const uint32_t id)
24212432
// - true if focus was set
24222433
bool Pane::FocusPane(const std::shared_ptr<Pane> pane)
24232434
{
2424-
return WalkTree([&](auto p) {
2435+
return WalkTree([&](const auto& p) {
24252436
if (p == pane)
24262437
{
24272438
p->_Focus();

src/cascadia/TerminalApp/Pane.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,7 @@ class Pane : public std::enable_shared_from_this<Pane>
248248

249249
std::optional<uint32_t> _id;
250250
std::weak_ptr<Pane> _parentChildPath{};
251-
251+
bool _closed{ false };
252252
bool _lastActive{ false };
253253
winrt::event_token _firstClosedToken{ 0 };
254254
winrt::event_token _secondClosedToken{ 0 };

src/cascadia/TerminalApp/TabManagement.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -739,7 +739,7 @@ namespace winrt::TerminalApp::implementation
739739
}
740740

741741
// Clean read-only mode to prevent additional prompt if closing the pane triggers closing of a hosting tab
742-
pane->WalkTree([](auto p) {
742+
pane->WalkTree([](const auto& p) {
743743
if (const auto control{ p->GetTerminalControl() })
744744
{
745745
if (control.ReadOnly())

src/cascadia/TerminalApp/TerminalPage.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -3812,7 +3812,7 @@ namespace winrt::TerminalApp::implementation
38123812
// recipe for disaster. We won't ever open up a tab in this window.
38133813
newTerminalArgs.Elevate(false);
38143814
const auto newPane = _MakePane(newTerminalArgs, nullptr, connection);
3815-
newPane->WalkTree([](auto pane) {
3815+
newPane->WalkTree([](const auto& pane) {
38163816
pane->FinalizeConfigurationGivenDefault();
38173817
});
38183818
_CreateNewTabFromPane(newPane);

src/cascadia/TerminalApp/TerminalTab.cpp

+18-24
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ namespace winrt::TerminalApp::implementation
4141

4242
auto firstId = _nextPaneId;
4343

44-
_rootPane->WalkTree([&](std::shared_ptr<Pane> pane) {
44+
_rootPane->WalkTree([&](const auto& pane) {
4545
// update the IDs on each pane
4646
if (pane->_IsLeaf())
4747
{
@@ -203,7 +203,7 @@ namespace winrt::TerminalApp::implementation
203203
{
204204
ASSERT_UI_THREAD();
205205

206-
_rootPane->WalkTree([&](std::shared_ptr<Pane> pane) {
206+
_rootPane->WalkTree([&](const auto& pane) {
207207
// Attach event handlers to each new pane
208208
_AttachEventHandlersToPane(pane);
209209
if (auto content = pane->GetContent())
@@ -275,7 +275,7 @@ namespace winrt::TerminalApp::implementation
275275
_UpdateHeaderControlMaxWidth();
276276

277277
// Update the settings on all our panes.
278-
_rootPane->WalkTree([&](auto pane) {
278+
_rootPane->WalkTree([&](const auto& pane) {
279279
pane->UpdateSettings(settings);
280280
return false;
281281
});
@@ -534,7 +534,7 @@ namespace winrt::TerminalApp::implementation
534534

535535
// Add the new event handlers to the new pane(s)
536536
// and update their ids.
537-
pane->WalkTree([&](auto p) {
537+
pane->WalkTree([&](const auto& p) {
538538
_AttachEventHandlersToPane(p);
539539
if (p->_IsLeaf())
540540
{
@@ -624,7 +624,7 @@ namespace winrt::TerminalApp::implementation
624624
// manually.
625625
_rootPane->Closed(_rootClosedToken);
626626
auto p = _rootPane;
627-
p->WalkTree([](auto pane) {
627+
p->WalkTree([](const auto& pane) {
628628
pane->Detached.raise(pane);
629629
});
630630

@@ -650,7 +650,7 @@ namespace winrt::TerminalApp::implementation
650650

651651
// Add the new event handlers to the new pane(s)
652652
// and update their ids.
653-
pane->WalkTree([&](auto p) {
653+
pane->WalkTree([&](const auto& p) {
654654
_AttachEventHandlersToPane(p);
655655
if (p->_IsLeaf())
656656
{
@@ -949,26 +949,20 @@ namespace winrt::TerminalApp::implementation
949949

950950
events.CloseRequested = content.CloseRequested(
951951
winrt::auto_revoke,
952-
[dispatcher, weakThis](auto sender, auto&&) -> winrt::fire_and_forget {
953-
// Don't forget! this ^^^^^^^^ sender can't be a reference, this is a async callback.
954-
955-
// The lambda lives in the `std::function`-style container owned by `control`. That is, when the
956-
// `control` gets destroyed the lambda struct also gets destroyed. In other words, we need to
957-
// copy `weakThis` onto the stack, because that's the only thing that gets captured in coroutines.
958-
// See: https://devblogs.microsoft.com/oldnewthing/20211103-00/?p=105870
959-
const auto weakThisCopy = weakThis;
960-
co_await wil::resume_foreground(dispatcher);
961-
// Check if Tab's lifetime has expired
962-
if (auto tab{ weakThisCopy.get() })
952+
[this](auto&& sender, auto&&) {
953+
if (const auto content{ sender.try_as<TerminalApp::IPaneContent>() })
963954
{
964-
if (const auto content{ sender.try_as<TerminalApp::IPaneContent>() })
955+
// Calling Close() while walking the tree is not safe, because Close() mutates the tree.
956+
const auto pane = _rootPane->_FindPane([&](const auto& p) -> std::shared_ptr<Pane> {
957+
if (p->GetContent() == content)
958+
{
959+
return p;
960+
}
961+
return {};
962+
});
963+
if (pane)
965964
{
966-
tab->_rootPane->WalkTree([content](std::shared_ptr<Pane> pane) {
967-
if (pane->GetContent() == content)
968-
{
969-
pane->Close();
970-
}
971-
});
965+
pane->Close();
972966
}
973967
}
974968
});

0 commit comments

Comments
 (0)