diff --git a/lldb/include/lldb/API/SBDebugger.h b/lldb/include/lldb/API/SBDebugger.h --- a/lldb/include/lldb/API/SBDebugger.h +++ b/lldb/include/lldb/API/SBDebugger.h @@ -88,6 +88,18 @@ FILE *GetErrorFileHandle(); + SBError SetInputFile(SBFile file); + + SBError SetOutputFile(SBFile file); + + SBError SetErrorFile(SBFile file); + + SBFile GetInputFile(); + + SBFile GetOutputFile(); + + SBFile GetErrorFile(); + void SaveInputTerminalState(); void RestoreInputTerminalState(); diff --git a/lldb/include/lldb/API/SBFile.h b/lldb/include/lldb/API/SBFile.h --- a/lldb/include/lldb/API/SBFile.h +++ b/lldb/include/lldb/API/SBFile.h @@ -14,6 +14,8 @@ namespace lldb { class LLDB_API SBFile { + friend class SBDebugger; + public: SBFile(); SBFile(FILE *file, bool transfer_ownership); @@ -31,6 +33,7 @@ private: FileSP m_opaque_sp; + SBFile(FileSP file_sp) : m_opaque_sp(file_sp) {} }; } // namespace lldb diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h --- a/lldb/include/lldb/Core/Debugger.h +++ b/lldb/include/lldb/Core/Debugger.h @@ -132,12 +132,12 @@ repro::DataRecorder *GetInputRecorder(); - void SetInputFileHandle(FILE *fh, bool tranfer_ownership, - repro::DataRecorder *recorder = nullptr); + Status SetInputFile(lldb::FileSP file, + repro::DataRecorder *recorder = nullptr); - void SetOutputFileHandle(FILE *fh, bool tranfer_ownership); + Status SetOutputFile(lldb::FileSP file); - void SetErrorFileHandle(FILE *fh, bool tranfer_ownership); + Status SetErrorFile(lldb::FileSP file); void SaveInputTerminalState(); 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 @@ -15,6 +15,15 @@ from lldbsuite.test.decorators import add_test_categories, no_debug_info_test +@contextmanager +def replace_stdout(new): + old = sys.stdout + sys.stdout = new + try: + yield + finally: + sys.stdout = old + def readStrippedLines(f): def i(): for line in f: @@ -66,10 +75,13 @@ interpreter.HandleCommand(cmd, ret) else: self.debugger.HandleCommand(cmd) + self.debugger.GetOutputFile().Flush() + self.debugger.GetErrorFile().Flush() if collect_result and check: self.assertTrue(ret.Succeeded()) return ret.GetOutput() + @add_test_categories(['pyapi']) @no_debug_info_test def test_legacy_file_out_script(self): @@ -95,6 +107,19 @@ with open(self.out_filename, 'r') as f: self.assertIn('deadbeef', f.read()) + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_legacy_file_err_with_get(self): + with open(self.out_filename, 'w') as f: + self.debugger.SetErrorFileHandle(f, False) + self.handleCmd('lolwut', check=False, collect_result=False) + self.debugger.GetErrorFileHandle().write('FOOBAR\n') + + with open(self.out_filename, 'r') as f: + errors = f.read() + self.assertTrue(re.search(r'error:.*lolwut', errors)) + self.assertTrue(re.search(r'FOOBAR', errors)) + @add_test_categories(['pyapi']) @no_debug_info_test @@ -146,3 +171,106 @@ self.assertTrue(e.Success()) self.assertEqual(buffer[:n], b'FOO') + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_fileno_out(self): + with open(self.out_filename, 'w') as f: + sbf = lldb.SBFile(f.fileno(), "w", False) + status = self.debugger.SetOutputFile(sbf) + if status.Fail(): + raise Exception(status) + self.handleCmd('script 1+2') + self.debugger.GetOutputFile().Write(b'quux') + + with open(self.out_filename, 'r') as f: + self.assertEqual(readStrippedLines(f), ['3', 'quux']) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_fileno_help(self): + with open(self.out_filename, 'w') as f: + sbf = lldb.SBFile(f.fileno(), "w", False) + status = self.debugger.SetOutputFile(sbf) + if status.Fail(): + raise Exception(status) + self.handleCmd("help help", collect_result=False, check=False) + with open(self.out_filename, 'r') as f: + self.assertTrue(re.search(r'Show a list of all debugger commands', f.read())) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_immediate(self): + with open(self.out_filename, 'w') as f: + ret = lldb.SBCommandReturnObject() + ret.SetImmediateOutputFile(f) + interpreter = self.debugger.GetCommandInterpreter() + interpreter.HandleCommand("help help", ret) + # make sure the file wasn't closed early. + f.write("\nQUUX\n") + + ret = None # call destructor and flush streams + + with open(self.out_filename, 'r') as f: + output = f.read() + self.assertTrue(re.search(r'Show a list of all debugger commands', output)) + self.assertTrue(re.search(r'QUUX', output)) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_fileno_inout(self): + with open(self.in_filename, 'w') as f: + f.write("help help\n") + + with open(self.out_filename, 'w') as outf, open(self.in_filename, 'r') as inf: + + outsbf = lldb.SBFile(outf.fileno(), "w", False) + status = self.debugger.SetOutputFile(outsbf) + if status.Fail(): + raise Exception(status) + + insbf = lldb.SBFile(inf.fileno(), "r", False) + status = self.debugger.SetInputFile(insbf) + if status.Fail(): + raise Exception(status) + + opts = lldb.SBCommandInterpreterRunOptions() + self.debugger.RunCommandInterpreter(True, False, opts, 0, False, False) + self.debugger.GetOutputFile().Flush() + + with open(self.out_filename, 'r') as f: + self.assertTrue(re.search(r'Show a list of all debugger commands', f.read())) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_fileno_error(self): + with open(self.out_filename, 'w') as f: + + sbf = lldb.SBFile(f.fileno(), 'w', False) + status = self.debugger.SetErrorFile(sbf) + if status.Fail(): + raise Exception(status) + + self.handleCmd('lolwut', check=False, collect_result=False) + + self.debugger.GetErrorFile().Write(b'\nzork\n') + + with open(self.out_filename, 'r') as f: + errors = f.read() + self.assertTrue(re.search(r'error:.*lolwut', errors)) + self.assertTrue(re.search(r'zork', errors)) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_replace_stdout(self): + f = io.StringIO() + with replace_stdout(f): + self.assertEqual(sys.stdout, f) + self.handleCmd('script sys.stdout.write("lol")', + collect_result=False, check=False) + self.assertEqual(sys.stdout, f) diff --git a/lldb/scripts/interface/SBDebugger.i b/lldb/scripts/interface/SBDebugger.i --- a/lldb/scripts/interface/SBDebugger.i +++ b/lldb/scripts/interface/SBDebugger.i @@ -183,6 +183,24 @@ FILE * GetErrorFileHandle (); + SBError + SetInputFile (SBFile file); + + SBError + SetOutputFile (SBFile file); + + SBError + SetErrorFile (SBFile file); + + SBFile + GetInputFile (); + + SBFile + GetOutputFile (); + + SBFile + GetErrorFile (); + lldb::SBCommandInterpreter GetCommandInterpreter (); diff --git a/lldb/source/API/SBDebugger.cpp b/lldb/source/API/SBDebugger.cpp --- a/lldb/source/API/SBDebugger.cpp +++ b/lldb/source/API/SBDebugger.cpp @@ -18,6 +18,7 @@ #include "lldb/API/SBCommandReturnObject.h" #include "lldb/API/SBError.h" #include "lldb/API/SBEvent.h" +#include "lldb/API/SBFile.h" #include "lldb/API/SBFrame.h" #include "lldb/API/SBListener.h" #include "lldb/API/SBProcess.h" @@ -304,30 +305,92 @@ if (loader) { llvm::Optional file = loader->GetNextFile(); fh = file ? FileSystem::Instance().Fopen(file->c_str(), "r") : nullptr; + transfer_ownership = true; } - m_opaque_sp->SetInputFileHandle(fh, transfer_ownership, recorder); + m_opaque_sp->SetInputFile(std::make_shared(fh, transfer_ownership), + recorder); +} + +SBError SBDebugger::SetInputFile(SBFile file) { + LLDB_RECORD_METHOD(SBError, SBDebugger, SetInputFile, (SBFile), file); + + SBError error; + if (!m_opaque_sp) { + error.ref().SetErrorString("invalid debugger"); + return error; + } + + repro::DataRecorder *recorder = nullptr; + if (repro::Generator *g = repro::Reproducer::Instance().GetGenerator()) + recorder = g->GetOrCreate().GetNewDataRecorder(); + + FileSP file_sp = file.m_opaque_sp; + + static std::unique_ptr loader = + repro::CommandLoader::Create(repro::Reproducer::Instance().GetLoader()); + if (loader) { + llvm::Optional nextfile = loader->GetNextFile(); + FILE *fh = nextfile ? FileSystem::Instance().Fopen(nextfile->c_str(), "r") + : nullptr; + if (fh) { + file_sp = std::make_shared(fh, true); + } + } + + error.SetError(m_opaque_sp->SetInputFile(file_sp, recorder)); + return error; } void SBDebugger::SetOutputFileHandle(FILE *fh, bool transfer_ownership) { LLDB_RECORD_METHOD(void, SBDebugger, SetOutputFileHandle, (FILE *, bool), fh, transfer_ownership); - if (m_opaque_sp) - m_opaque_sp->SetOutputFileHandle(fh, transfer_ownership); + if (m_opaque_sp) { + m_opaque_sp->SetOutputFile(std::make_shared(fh, transfer_ownership)); + } +} + +SBError SBDebugger::SetOutputFile(SBFile file) { + LLDB_RECORD_METHOD(SBError, SBDebugger, SetOutputFile, (SBFile file), file); + SBError error; + if (!m_opaque_sp) { + error.ref().SetErrorString("invalid debugger"); + return error; + } + if (!file) { + error.ref().SetErrorString("invalid file"); + return error; + } + error.SetError(m_opaque_sp->SetOutputFile(file.m_opaque_sp)); + return error; } void SBDebugger::SetErrorFileHandle(FILE *fh, bool transfer_ownership) { LLDB_RECORD_METHOD(void, SBDebugger, SetErrorFileHandle, (FILE *, bool), fh, transfer_ownership); + if (m_opaque_sp) { + m_opaque_sp->SetErrorFile(std::make_shared(fh, transfer_ownership)); + } +} - if (m_opaque_sp) - m_opaque_sp->SetErrorFileHandle(fh, transfer_ownership); +SBError SBDebugger::SetErrorFile(SBFile file) { + LLDB_RECORD_METHOD(SBError, SBDebugger, SetErrorFile, (SBFile file), file); + SBError error; + if (!m_opaque_sp) { + error.ref().SetErrorString("invalid debugger"); + return error; + } + if (!file) { + error.ref().SetErrorString("invalid file"); + return error; + } + error.SetError(m_opaque_sp->SetErrorFile(file.m_opaque_sp)); + return error; } FILE *SBDebugger::GetInputFileHandle() { LLDB_RECORD_METHOD_NO_ARGS(FILE *, SBDebugger, GetInputFileHandle); - if (m_opaque_sp) { File &file_sp = m_opaque_sp->GetInputFile(); return LLDB_RECORD_RESULT(file_sp.GetStream()); @@ -335,9 +398,16 @@ return nullptr; } +SBFile SBDebugger::GetInputFile() { + LLDB_RECORD_METHOD_NO_ARGS(SBFile, SBDebugger, GetInputFile); + if (m_opaque_sp) { + return LLDB_RECORD_RESULT(SBFile(m_opaque_sp->GetInputFileSP())); + } + return LLDB_RECORD_RESULT(SBFile()); +} + FILE *SBDebugger::GetOutputFileHandle() { LLDB_RECORD_METHOD_NO_ARGS(FILE *, SBDebugger, GetOutputFileHandle); - if (m_opaque_sp) { StreamFile &stream_file = m_opaque_sp->GetOutputStream(); return LLDB_RECORD_RESULT(stream_file.GetFile().GetStream()); @@ -345,6 +415,15 @@ return nullptr; } +SBFile SBDebugger::GetOutputFile() { + LLDB_RECORD_METHOD_NO_ARGS(SBFile, SBDebugger, GetOutputFile); + if (m_opaque_sp) { + SBFile file(m_opaque_sp->GetOutputStream().GetFileSP()); + return LLDB_RECORD_RESULT(file); + } + return LLDB_RECORD_RESULT(SBFile()); +} + FILE *SBDebugger::GetErrorFileHandle() { LLDB_RECORD_METHOD_NO_ARGS(FILE *, SBDebugger, GetErrorFileHandle); @@ -355,6 +434,16 @@ return nullptr; } +SBFile SBDebugger::GetErrorFile() { + LLDB_RECORD_METHOD_NO_ARGS(SBFile, SBDebugger, GetErrorFile); + SBFile file; + if (m_opaque_sp) { + SBFile file(m_opaque_sp->GetErrorStream().GetFileSP()); + return LLDB_RECORD_RESULT(file); + } + return LLDB_RECORD_RESULT(SBFile()); +} + void SBDebugger::SaveInputTerminalState() { LLDB_RECORD_METHOD_NO_ARGS(void, SBDebugger, SaveInputTerminalState); @@ -1503,6 +1592,8 @@ // Do nothing. } +static SBError SetFileRedirect(SBDebugger *, SBFile file) { return SBError(); } + static bool GetDefaultArchitectureRedirect(char *arch_name, size_t arch_name_len) { // The function is writing to its argument. Without the redirect it would @@ -1523,6 +1614,16 @@ &SBDebugger::GetDefaultArchitecture), &GetDefaultArchitectureRedirect); + R.Register(&invoke::method<&SBDebugger::SetInputFile>::doit, + &SetFileRedirect); + R.Register(&invoke::method<&SBDebugger::SetOutputFile>::doit, + &SetFileRedirect); + R.Register(&invoke::method<&SBDebugger::SetErrorFile>::doit, + &SetFileRedirect); + LLDB_REGISTER_CONSTRUCTOR(SBDebugger, ()); LLDB_REGISTER_CONSTRUCTOR(SBDebugger, (const lldb::DebuggerSP &)); LLDB_REGISTER_CONSTRUCTOR(SBDebugger, (const lldb::SBDebugger &)); @@ -1547,6 +1648,9 @@ LLDB_REGISTER_METHOD(FILE *, SBDebugger, GetInputFileHandle, ()); LLDB_REGISTER_METHOD(FILE *, SBDebugger, GetOutputFileHandle, ()); LLDB_REGISTER_METHOD(FILE *, SBDebugger, GetErrorFileHandle, ()); + LLDB_REGISTER_METHOD(SBFile, SBDebugger, GetInputFile, ()); + LLDB_REGISTER_METHOD(SBFile, SBDebugger, GetOutputFile, ()); + LLDB_REGISTER_METHOD(SBFile, SBDebugger, GetErrorFile, ()); LLDB_REGISTER_METHOD(void, SBDebugger, SaveInputTerminalState, ()); LLDB_REGISTER_METHOD(void, SBDebugger, RestoreInputTerminalState, ()); LLDB_REGISTER_METHOD(lldb::SBCommandInterpreter, SBDebugger, diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp --- a/lldb/source/Core/Debugger.cpp +++ b/lldb/source/Core/Debugger.cpp @@ -821,32 +821,42 @@ repro::DataRecorder *Debugger::GetInputRecorder() { return m_input_recorder; } -void Debugger::SetInputFileHandle(FILE *fh, bool tranfer_ownership, - repro::DataRecorder *recorder) { +Status Debugger::SetInputFile(FileSP file_sp, repro::DataRecorder *recorder) { + Status error; m_input_recorder = recorder; - m_input_file_sp = std::make_shared(fh, tranfer_ownership); - if (!m_input_file_sp->IsValid()) + if (!file_sp || !file_sp->IsValid()) { m_input_file_sp = std::make_shared(stdin, false); + error.SetErrorString("invalid file"); + } else { + m_input_file_sp = file_sp; + } // Save away the terminal state if that is relevant, so that we can restore // it in RestoreInputState. SaveInputTerminalState(); + + return error; } -void Debugger::SetOutputFileHandle(FILE *fh, bool tranfer_ownership) { - FileSP file_sp = std::make_shared(fh, tranfer_ownership); - if (!file_sp->IsValid()) +Status Debugger::SetOutputFile(FileSP file_sp) { + Status error; + if (!file_sp || !file_sp->IsValid()) { file_sp = std::make_shared(stdout, false); + error.SetErrorString("invalid file"); + } m_output_stream_sp = std::make_shared(file_sp); - + return error; } -void Debugger::SetErrorFileHandle(FILE *fh, bool tranfer_ownership) { - FileSP file_sp = std::make_shared(fh, tranfer_ownership); - if (!file_sp->IsValid()) +Status Debugger::SetErrorFile(FileSP file_sp) { + Status error; + if (!file_sp || !file_sp->IsValid()) { file_sp = std::make_shared(stderr, false); + error.SetErrorString("invalid file"); + } m_error_stream_sp = std::make_shared(file_sp); + return error; } void Debugger::SaveInputTerminalState() {