diff --git a/lldb/include/lldb/Core/IOHandler.h b/lldb/include/lldb/Core/IOHandler.h --- a/lldb/include/lldb/Core/IOHandler.h +++ b/lldb/include/lldb/Core/IOHandler.h @@ -431,6 +431,7 @@ bool m_interrupt_exits; bool m_editing; // Set to true when fetching a line manually (not using // libedit) + std::string m_line_buffer; }; // The order of base classes is important. Look at the constructor of diff --git a/lldb/packages/Python/lldbsuite/test/python_api/file_handle/TestFileHandle.py b/lldb/packages/Python/lldbsuite/test/python_api/file_handle/TestFileHandle.py --- a/lldb/packages/Python/lldbsuite/test/python_api/file_handle/TestFileHandle.py +++ b/lldb/packages/Python/lldbsuite/test/python_api/file_handle/TestFileHandle.py @@ -400,9 +400,9 @@ @add_test_categories(['pyapi']) - @expectedFailure # FIXME IOHandler still using FILE* + @skipIf(py_version=['<', (3,)]) def test_string_inout(self): - inf = io.StringIO("help help\n") + inf = io.StringIO("help help\np/x ~0\n") outf = io.StringIO() status = self.debugger.SetOutputFile(lldb.SBFile(outf)) self.assertTrue(status.Success()) @@ -413,10 +413,11 @@ self.debugger.GetOutputFile().Flush() output = outf.getvalue() self.assertIn('Show a list of all debugger commands', output) + self.assertIn('0xfff', output) @add_test_categories(['pyapi']) - @expectedFailure # FIXME IOHandler still using FILE* + @skipIf(py_version=['<', (3,)]) def test_bytes_inout(self): inf = io.BytesIO(b"help help\nhelp b\n") outf = io.BytesIO() diff --git a/lldb/source/Core/IOHandler.cpp b/lldb/source/Core/IOHandler.cpp --- a/lldb/source/Core/IOHandler.cpp +++ b/lldb/source/Core/IOHandler.cpp @@ -305,94 +305,145 @@ m_delegate.IOHandlerDeactivated(*this); } +using llvm::None; +using llvm::Optional; +using llvm::StringRef; + +// Split out a line from the buffer, if there is a full one to get. +static Optional SplitLine(std::string &line_buffer) { + size_t pos = line_buffer.find('\n'); + if (pos == std::string::npos) + return None; + size_t end = pos + 1; + while (end > 0 && + (line_buffer[end - 1] == '\n' || line_buffer[end - 1] == '\r')) + end--; + std::string line = line_buffer.substr(0, end); + line_buffer = line_buffer.substr(pos + 1); + return line; +} + +// Append newchars to the buffer and split out a line. +static Optional AppendAndSplitLine(std::string &line_buffer, + StringRef newchars) { + size_t pos = newchars.find('\n'); + if (pos == StringRef::npos) { + line_buffer.append(newchars); + return None; + } + size_t end = pos + 1; + while (end > 0 && (newchars[end - 1] == '\n' || newchars[end - 1] == '\r')) + end--; + std::string line = std::move(line_buffer); + line.append(newchars.substr(0, end)); + line_buffer = newchars.substr(pos + 1); + return line; +} + +// If the final line of the file ends without a end-of-line, return +// it as a line anyway. +static Optional SplitLineEOF(std::string &line_buffer) { + if (line_buffer.empty()) + return None; + if (std::all_of(line_buffer.begin(), line_buffer.end(), isspace)) + return None; + std::string line = std::move(line_buffer); + line_buffer.clear(); + return line; +} + bool IOHandlerEditline::GetLine(std::string &line, bool &interrupted) { #ifndef LLDB_DISABLE_LIBEDIT if (m_editline_up) { bool b = m_editline_up->GetLine(line, interrupted); - if (m_data_recorder) + if (b && m_data_recorder) m_data_recorder->Record(line, true); return b; - } else { + } #endif - line.clear(); - FILE *in = GetInputFILE(); - if (in) { - if (GetIsInteractive()) { - const char *prompt = nullptr; + line.clear(); - if (m_multi_line && m_curr_line_idx > 0) - prompt = GetContinuationPrompt(); + if (GetIsInteractive()) { + const char *prompt = nullptr; - if (prompt == nullptr) - prompt = GetPrompt(); + if (m_multi_line && m_curr_line_idx > 0) + prompt = GetContinuationPrompt(); - if (prompt && prompt[0]) { - if (m_output_sp) { - m_output_sp->Printf("%s", prompt); - m_output_sp->Flush(); - } - } + if (prompt == nullptr) + prompt = GetPrompt(); + + if (prompt && prompt[0]) { + if (m_output_sp) { + m_output_sp->Printf("%s", prompt); + m_output_sp->Flush(); + } + } + } + + Optional got_line = SplitLine(m_line_buffer); + + if (!got_line && !m_input_sp) { + // No more input file, we are done... + SetIsDone(true); + return false; + } + + FILE *in = GetInputFILE(); + char buffer[256]; + + if (!got_line && !in && m_input_sp) { + // there is no FILE*, fall back on just reading bytes from the stream. + while (!got_line) { + size_t bytes_read = sizeof(buffer); + Status error = m_input_sp->Read((void *)buffer, bytes_read); + if (error.Success() && !bytes_read) { + got_line = SplitLineEOF(m_line_buffer); + break; } - char buffer[256]; - bool done = false; - bool got_line = false; - m_editing = true; - while (!done) { + if (error.Fail()) + break; + got_line = + AppendAndSplitLine(m_line_buffer, StringRef(buffer, bytes_read)); + } + } + + if (!got_line && in) { + m_editing = true; + while (!got_line) { + char *r = fgets(buffer, sizeof(buffer), in); #ifdef _WIN32 - // ReadFile on Windows is supposed to set ERROR_OPERATION_ABORTED - // according to the docs on MSDN. However, this has evidently been a - // known bug since Windows 8. Therefore, we can't detect if a signal - // interrupted in the fgets. So pressing ctrl-c causes the repl to end - // and the process to exit. A temporary workaround is just to attempt to - // fgets twice until this bug is fixed. - if (fgets(buffer, sizeof(buffer), in) == nullptr && - fgets(buffer, sizeof(buffer), in) == nullptr) { - // this is the equivalent of EINTR for Windows - if (GetLastError() == ERROR_OPERATION_ABORTED) - continue; -#else - if (fgets(buffer, sizeof(buffer), in) == nullptr) { + // ReadFile on Windows is supposed to set ERROR_OPERATION_ABORTED + // according to the docs on MSDN. However, this has evidently been a + // known bug since Windows 8. Therefore, we can't detect if a signal + // interrupted in the fgets. So pressing ctrl-c causes the repl to end + // and the process to exit. A temporary workaround is just to attempt to + // fgets twice until this bug is fixed. + if (r == nullptr) + r = fgets(buffer, sizeof(buffer), in); + // this is the equivalent of EINTR for Windows + if (r == nullptr && GetLastError() == ERROR_OPERATION_ABORTED) + continue; #endif - const int saved_errno = errno; - if (feof(in)) - done = true; - else if (ferror(in)) { - if (saved_errno != EINTR) - done = true; - } - } else { - got_line = true; - size_t buffer_len = strlen(buffer); - assert(buffer[buffer_len] == '\0'); - char last_char = buffer[buffer_len - 1]; - if (last_char == '\r' || last_char == '\n') { - done = true; - // Strip trailing newlines - while (last_char == '\r' || last_char == '\n') { - --buffer_len; - if (buffer_len == 0) - break; - last_char = buffer[buffer_len - 1]; - } - } - line.append(buffer, buffer_len); - } + if (r == nullptr) { + if (ferror(in) && errno == EINTR) + continue; + if (feof(in)) + got_line = SplitLineEOF(m_line_buffer); + break; } - m_editing = false; - if (m_data_recorder && got_line) - m_data_recorder->Record(line, true); - // We might have gotten a newline on a line by itself make sure to return - // true in this case. - return got_line; - } else { - // No more input file, we are done... - SetIsDone(true); + got_line = AppendAndSplitLine(m_line_buffer, buffer); } - return false; -#ifndef LLDB_DISABLE_LIBEDIT + m_editing = false; } -#endif + + if (got_line) { + line = got_line.getValue(); + if (m_data_recorder) + m_data_recorder->Record(line, true); + } + + return (bool)got_line; } #ifndef LLDB_DISABLE_LIBEDIT