Skip to content

Commit 8ec8cfb

Browse files
authored
Merge pull request #10523 from swiftlang/cherrypick-swift/release/6.2-5f99e0d4b9ea-83116209331a
[🍒 swift/release/6.2] [lldb] Fix lock inversion between statusline mutex and output mutex (llvm#135956)
2 parents b10858e + 9d7ff58 commit 8ec8cfb

File tree

3 files changed

+92
-47
lines changed

3 files changed

+92
-47
lines changed

lldb/source/Core/Statusline.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#define ANSI_CLEAR_LINE ESCAPE "[2K"
2929
#define ANSI_SET_SCROLL_ROWS ESCAPE "[0;%ur"
3030
#define ANSI_TO_START_OF_ROW ESCAPE "[%u;0f"
31+
#define ANSI_REVERSE_VIDEO ESCAPE "[7m"
3132
#define ANSI_UP_ROWS ESCAPE "[%dA"
3233

3334
using namespace lldb;
@@ -75,6 +76,12 @@ void Statusline::Draw(std::string str) {
7576
locked_stream << ANSI_SAVE_CURSOR;
7677
locked_stream.Printf(ANSI_TO_START_OF_ROW,
7778
static_cast<unsigned>(m_terminal_height));
79+
80+
// Use "reverse video" to make sure the statusline has a background. Only do
81+
// this when colors are disabled, and rely on the statusline format otherwise.
82+
if (!m_debugger.GetUseColor())
83+
locked_stream << ANSI_REVERSE_VIDEO;
84+
7885
locked_stream << str;
7986
locked_stream << ANSI_NORMAL;
8087
locked_stream << ANSI_RESTORE_CURSOR;

lldb/source/Host/common/Editline.cpp

Lines changed: 46 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -567,9 +567,6 @@ int Editline::GetCharacter(EditLineGetCharType *c) {
567567
m_needs_prompt_repaint = false;
568568
}
569569

570-
if (m_redraw_callback)
571-
m_redraw_callback();
572-
573570
if (m_multiline_enabled) {
574571
// Detect when the number of rows used for this input line changes due to
575572
// an edit
@@ -585,54 +582,56 @@ int Editline::GetCharacter(EditLineGetCharType *c) {
585582
m_current_line_rows = new_line_rows;
586583
}
587584

585+
if (m_terminal_size_has_changed)
586+
ApplyTerminalSizeChange();
587+
588+
// This mutex is locked by our caller (GetLine). Unlock it while we read a
589+
// character (blocking operation), so we do not hold the mutex
590+
// indefinitely. This gives a chance for someone to interrupt us. After
591+
// Read returns, immediately lock the mutex again and check if we were
592+
// interrupted.
593+
m_locked_output.reset();
594+
595+
if (m_redraw_callback)
596+
m_redraw_callback();
597+
588598
// Read an actual character
589-
while (true) {
590-
lldb::ConnectionStatus status = lldb::eConnectionStatusSuccess;
591-
char ch = 0;
592-
593-
if (m_terminal_size_has_changed)
594-
ApplyTerminalSizeChange();
595-
596-
// This mutex is locked by our caller (GetLine). Unlock it while we read a
597-
// character (blocking operation), so we do not hold the mutex
598-
// indefinitely. This gives a chance for someone to interrupt us. After
599-
// Read returns, immediately lock the mutex again and check if we were
600-
// interrupted.
601-
m_locked_output.reset();
602-
int read_count =
603-
m_input_connection.Read(&ch, 1, std::nullopt, status, nullptr);
604-
m_locked_output.emplace(m_output_stream_sp->Lock());
605-
if (m_editor_status == EditorStatus::Interrupted) {
606-
while (read_count > 0 && status == lldb::eConnectionStatusSuccess)
607-
read_count =
608-
m_input_connection.Read(&ch, 1, std::nullopt, status, nullptr);
609-
lldbassert(status == lldb::eConnectionStatusInterrupted);
610-
return 0;
611-
}
599+
lldb::ConnectionStatus status = lldb::eConnectionStatusSuccess;
600+
char ch = 0;
601+
int read_count =
602+
m_input_connection.Read(&ch, 1, std::nullopt, status, nullptr);
603+
604+
// Re-lock the output mutex to protected m_editor_status here and in the
605+
// switch below.
606+
m_locked_output.emplace(m_output_stream_sp->Lock());
607+
if (m_editor_status == EditorStatus::Interrupted) {
608+
while (read_count > 0 && status == lldb::eConnectionStatusSuccess)
609+
read_count =
610+
m_input_connection.Read(&ch, 1, std::nullopt, status, nullptr);
611+
lldbassert(status == lldb::eConnectionStatusInterrupted);
612+
return 0;
613+
}
612614

613-
if (read_count) {
614-
if (CompleteCharacter(ch, *c))
615-
return 1;
616-
} else {
617-
switch (status) {
618-
case lldb::eConnectionStatusSuccess: // Success
619-
break;
615+
if (read_count) {
616+
if (CompleteCharacter(ch, *c))
617+
return 1;
618+
return 0;
619+
}
620620

621-
case lldb::eConnectionStatusInterrupted:
622-
llvm_unreachable("Interrupts should have been handled above.");
623-
624-
case lldb::eConnectionStatusError: // Check GetError() for details
625-
case lldb::eConnectionStatusTimedOut: // Request timed out
626-
case lldb::eConnectionStatusEndOfFile: // End-of-file encountered
627-
case lldb::eConnectionStatusNoConnection: // No connection
628-
case lldb::eConnectionStatusLostConnection: // Lost connection while
629-
// connected to a valid
630-
// connection
631-
m_editor_status = EditorStatus::EndOfInput;
632-
return 0;
633-
}
634-
}
621+
switch (status) {
622+
case lldb::eConnectionStatusSuccess:
623+
llvm_unreachable("Success should have resulted in positive read_count.");
624+
case lldb::eConnectionStatusInterrupted:
625+
llvm_unreachable("Interrupts should have been handled above.");
626+
case lldb::eConnectionStatusError:
627+
case lldb::eConnectionStatusTimedOut:
628+
case lldb::eConnectionStatusEndOfFile:
629+
case lldb::eConnectionStatusNoConnection:
630+
case lldb::eConnectionStatusLostConnection:
631+
m_editor_status = EditorStatus::EndOfInput;
635632
}
633+
634+
return 0;
636635
}
637636

638637
const char *Editline::Prompt() {

lldb/test/API/functionalities/statusline/TestStatusline.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,42 @@ def test(self):
5555
self.expect(
5656
"set set show-statusline false", ["\x1b[0;{}r".format(terminal_height)]
5757
)
58+
59+
# PExpect uses many timeouts internally and doesn't play well
60+
# under ASAN on a loaded machine..
61+
@skipIfAsan
62+
def test_no_color(self):
63+
"""Basic test for the statusline with colors disabled."""
64+
self.build()
65+
self.launch(use_colors=False)
66+
self.do_setup()
67+
68+
# Change the terminal dimensions.
69+
terminal_height = 10
70+
terminal_width = 60
71+
self.child.setwinsize(terminal_height, terminal_width)
72+
73+
# Enable the statusline and check for the "reverse video" control character.
74+
self.expect(
75+
"set set show-statusline true",
76+
[
77+
"\x1b[7m",
78+
],
79+
)
80+
81+
def test_deadlock(self):
82+
"""Regression test for lock inversion between the statusline mutex and
83+
the output mutex."""
84+
self.build()
85+
self.launch(extra_args=["-o", "settings set use-color false"])
86+
self.child.expect("(lldb)")
87+
88+
# Change the terminal dimensions.
89+
terminal_height = 10
90+
terminal_width = 60
91+
self.child.setwinsize(terminal_height, terminal_width)
92+
93+
exe = self.getBuildArtifact("a.out")
94+
95+
self.expect("file {}".format(exe), ["Current executable"])
96+
self.expect("help", ["Debugger commands"])

0 commit comments

Comments
 (0)