diff --git a/lldb/include/lldb/API/LLDB.h b/lldb/include/lldb/API/LLDB.h --- a/lldb/include/lldb/API/LLDB.h +++ b/lldb/include/lldb/API/LLDB.h @@ -29,6 +29,7 @@ #include "lldb/API/SBExecutionContext.h" #include "lldb/API/SBExpressionOptions.h" #include "lldb/API/SBFileSpec.h" +#include "lldb/API/SBFile.h" #include "lldb/API/SBFileSpecList.h" #include "lldb/API/SBFrame.h" #include "lldb/API/SBFunction.h" diff --git a/lldb/include/lldb/API/SBCommandReturnObject.h b/lldb/include/lldb/API/SBCommandReturnObject.h --- a/lldb/include/lldb/API/SBCommandReturnObject.h +++ b/lldb/include/lldb/API/SBCommandReturnObject.h @@ -40,13 +40,21 @@ const char *GetError(); - size_t PutOutput(FILE *fh); + size_t PutOutput(FILE *fh); // DEPRECATED + + size_t PutOutput(SBFile &file); + + size_t PutOutput(lldb_private::File &file); size_t GetOutputSize(); size_t GetErrorSize(); - size_t PutError(FILE *fh); + size_t PutError(FILE *fh); // DEPRECATED + + size_t PutError(SBFile &file); + + size_t PutError(lldb_private::File &file); void Clear(); @@ -64,14 +72,21 @@ bool GetDescription(lldb::SBStream &description); - // deprecated, these two functions do not take ownership of file handle - void SetImmediateOutputFile(FILE *fh); + void SetImmediateOutputFile(FILE *fh); // DEPRECATED + + void SetImmediateErrorFile(FILE *fh); // DEPRECATED + + void SetImmediateOutputFile(FILE *fh, bool transfer_ownership); // DEPRECATED + + void SetImmediateErrorFile(FILE *fh, bool transfer_ownership); // DEPRECATED + + void SetImmediateOutputFile(SBFile &file); - void SetImmediateErrorFile(FILE *fh); + void SetImmediateErrorFile(SBFile &file); - void SetImmediateOutputFile(FILE *fh, bool transfer_ownership); + void SetImmediateOutputFile(lldb_private::File &file); - void SetImmediateErrorFile(FILE *fh, bool transfer_ownership); + void SetImmediateErrorFile(lldb_private::File &file); void PutCString(const char *string, int len = -1); 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,24 @@ FILE *GetErrorFileHandle(); + SBError SetInputFile(SBFile &file); + + SBError SetOutputFile(SBFile &file); + + SBError SetErrorFile(SBFile &file); + + SBError SetInputFile(lldb_private::File &file); + + SBError SetOutputFile(lldb_private::File &file); + + SBError SetErrorFile(lldb_private::File &file); + + SBFile GetInputFile(); + + SBFile GetOutputFile(); + + SBFile GetErrorFile(); + void SaveInputTerminalState(); void RestoreInputTerminalState(); @@ -99,7 +117,18 @@ lldb::SBListener GetListener(); void HandleProcessEvent(const lldb::SBProcess &process, - const lldb::SBEvent &event, FILE *out, FILE *err); + const lldb::SBEvent &event, FILE *out, FILE *err); // DEPRECATED + + void HandleProcessEvent(const lldb::SBProcess &process, + const lldb::SBEvent &event, SBFile &out, SBFile &err); + + void HandleProcessEvent(const lldb::SBProcess &process, + const lldb::SBEvent &event, lldb_private::File &out, lldb_private::File &err); + + void HandleProcessEvent(const lldb::SBProcess &process, + const lldb::SBEvent &event, SBFile &&out, SBFile &&err) { + HandleProcessEvent(process, event, out, err); + } lldb::SBTarget CreateTarget(const char *filename, const char *target_triple, const char *platform_name, diff --git a/lldb/include/lldb/API/SBDefines.h b/lldb/include/lldb/API/SBDefines.h --- a/lldb/include/lldb/API/SBDefines.h +++ b/lldb/include/lldb/API/SBDefines.h @@ -92,6 +92,7 @@ class LLDB_API SBVariablesOptions; class LLDB_API SBWatchpoint; class LLDB_API SBUnixSignals; +class LLDB_API SBFile; typedef bool (*SBBreakpointHitCallback)(void *baton, SBProcess &process, SBThread &thread, diff --git a/lldb/include/lldb/API/SBError.h b/lldb/include/lldb/API/SBError.h --- a/lldb/include/lldb/API/SBError.h +++ b/lldb/include/lldb/API/SBError.h @@ -70,6 +70,7 @@ friend class SBTrace; friend class SBValue; friend class SBWatchpoint; + friend class SBFile; lldb_private::Status *get(); diff --git a/lldb/include/lldb/API/SBFile.h b/lldb/include/lldb/API/SBFile.h new file mode 100644 --- /dev/null +++ b/lldb/include/lldb/API/SBFile.h @@ -0,0 +1,51 @@ +//===-- SBFile.h --------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_SBFile_h_ +#define LLDB_SBFile_h_ + +#include "lldb/API/SBDefines.h" + +namespace lldb { + +class LLDB_API SBFile { +public: + + SBFile (const SBFile &file); + SBFile (SBFile &&file); + SBFile(); + ~SBFile(); + + SBFile &operator= (const SBFile &file); + void SetStream(FILE *file, bool transfer_ownership); + void SetDescriptor(int fd, bool transfer_ownership); + + // It seems odd that we have a private type in the argument list of a SB method. + // This is here for scripting clients, not C++ clients. They will pass their own + // native file objects in, and the SWIG typemaps will convert it to a + // lldb_private::File + void SetFile(lldb_private::File &file); + + SBError Read(uint8_t *buf, size_t num_bytes, size_t *bytes_read); + SBError Write(const uint8_t *buf, size_t num_bytes, size_t *bytes_written); + SBError Flush(); + bool IsValid() const; + SBError Close(); + + operator bool() const { return IsValid(); } + bool operator !() const { return !IsValid(); } + + lldb_private::File &GetFile() const { return *m_opaque_up; } + +private: + FileUP m_opaque_up; +}; + +} // namespace lldb + +#endif // LLDB_SBFile_h_ diff --git a/lldb/include/lldb/API/SBInstruction.h b/lldb/include/lldb/API/SBInstruction.h --- a/lldb/include/lldb/API/SBInstruction.h +++ b/lldb/include/lldb/API/SBInstruction.h @@ -55,6 +55,10 @@ void Print(FILE *out); + void Print(SBFile &out); + + void Print(lldb_private::File &out); + bool GetDescription(lldb::SBStream &description); bool EmulateWithFrame(lldb::SBFrame &frame, uint32_t evaluate_options); diff --git a/lldb/include/lldb/API/SBInstructionList.h b/lldb/include/lldb/API/SBInstructionList.h --- a/lldb/include/lldb/API/SBInstructionList.h +++ b/lldb/include/lldb/API/SBInstructionList.h @@ -46,6 +46,10 @@ void Print(FILE *out); + void Print(SBFile &out); + + void Print(lldb_private::File &out); + bool GetDescription(lldb::SBStream &description); bool DumpEmulationForAllInstructions(const char *triple); @@ -56,6 +60,8 @@ friend class SBTarget; void SetDisassembler(const lldb::DisassemblerSP &opaque_sp); + bool GetDescription(lldb_private::Stream &description); + private: lldb::DisassemblerSP m_opaque_sp; diff --git a/lldb/include/lldb/API/SBProcess.h b/lldb/include/lldb/API/SBProcess.h --- a/lldb/include/lldb/API/SBProcess.h +++ b/lldb/include/lldb/API/SBProcess.h @@ -67,6 +67,10 @@ void ReportEventState(const lldb::SBEvent &event, FILE *out) const; + void ReportEventState(const lldb::SBEvent &event, SBFile &file) const; + + void ReportEventState(const lldb::SBEvent &event, lldb_private::File &file) const; + void AppendEventStateReport(const lldb::SBEvent &event, lldb::SBCommandReturnObject &result); diff --git a/lldb/include/lldb/API/SBStream.h b/lldb/include/lldb/API/SBStream.h --- a/lldb/include/lldb/API/SBStream.h +++ b/lldb/include/lldb/API/SBStream.h @@ -39,6 +39,10 @@ void RedirectToFile(const char *path, bool append); + void RedirectToFile(SBFile &file); + + void RedirectToFile(lldb_private::File &file); + void RedirectToFileHandle(FILE *fh, bool transfer_fh_ownership); void RedirectToFileDescriptor(int fd, bool transfer_fh_ownership); 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 @@ -121,12 +121,11 @@ repro::DataRecorder *GetInputRecorder(); - void SetInputFileHandle(FILE *fh, bool tranfer_ownership, - repro::DataRecorder *recorder = nullptr); + Status SetInputFile(File &file, repro::DataRecorder *recorder = nullptr); - void SetOutputFileHandle(FILE *fh, bool tranfer_ownership); + Status SetOutputFile(File &file); - void SetErrorFileHandle(FILE *fh, bool tranfer_ownership); + Status SetErrorFile(File &file); void SaveInputTerminalState(); 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 @@ -432,6 +432,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/include/lldb/Core/StreamFile.h b/lldb/include/lldb/Core/StreamFile.h --- a/lldb/include/lldb/Core/StreamFile.h +++ b/lldb/include/lldb/Core/StreamFile.h @@ -35,6 +35,8 @@ StreamFile(FILE *fh, bool transfer_ownership); + StreamFile(File &file); + ~StreamFile() override; File &GetFile() { return m_file; } diff --git a/lldb/include/lldb/Host/File.h b/lldb/include/lldb/Host/File.h --- a/lldb/include/lldb/Host/File.h +++ b/lldb/include/lldb/Host/File.h @@ -21,12 +21,67 @@ namespace lldb_private { +class FileOps { + friend class File; + friend class PythonFile; +public: + enum FileOpsKind { + FOK_FileOps = 0, + FOK_SimplePythonFileOps, + FOK_BinaryPythonFileOps, + FOK_TextPythonFileOps, + FOK_Last_SimplePythonFileOps, + }; + FileOpsKind GetKind() const { return m_kind; } + static const int kInvalidDescriptor = -1; + FileOps() : + m_descriptor(kInvalidDescriptor), + m_stream(nullptr), + m_own_descriptor(false), + m_own_stream(false), + m_overrides_io(false), + m_kind(FOK_FileOps) {}; + FileOps(FILE *stream, bool take_ownership) : + m_descriptor(kInvalidDescriptor), + m_stream(stream), + m_own_descriptor(false), + m_own_stream(take_ownership), + m_overrides_io(false), + m_kind(FOK_FileOps) {}; + FileOps(int descriptor, bool take_ownership) : + m_descriptor(descriptor), + m_stream(nullptr), + m_own_descriptor(take_ownership), + m_own_stream(false), + m_overrides_io(false), + m_kind(FOK_FileOps) {}; + virtual Status Close(); + virtual ~FileOps(); + virtual Status Write(const void *buf, size_t &num_bytes); + virtual Status Read(void *buf, size_t &num_bytes); + virtual Status Flush(); + +protected: + int m_descriptor; + FILE *m_stream; + bool m_own_descriptor; + bool m_own_stream; + + // If this is false the the FileOps is only here to manage closing + // the stream or descriptor when all the Files that refer to it are + // closed. If it's true then actual io operations will be routed + // through the FileOps. + bool m_overrides_io; + FileOpsKind m_kind; +}; + /// \class File File.h "lldb/Host/File.h" /// A file class. /// /// A file class that divides abstracts the LLDB core from host file /// functionality. class File : public IOObject { + friend class PythonFile; public: static int kInvalidDescriptor; static FILE *kInvalidStream; @@ -51,24 +106,50 @@ static mode_t ConvertOpenOptionsForPOSIXOpen(uint32_t open_options); File() - : IOObject(eFDTypeFile, false), m_descriptor(kInvalidDescriptor), - m_stream(kInvalidStream), m_options(0), m_own_stream(false), + : IOObject(eFDTypeFile), m_descriptor(kInvalidDescriptor), + m_stream(kInvalidStream), m_options(0), m_is_interactive(eLazyBoolCalculate), m_is_real_terminal(eLazyBoolCalculate), m_supports_colors(eLazyBoolCalculate) {} File(FILE *fh, bool transfer_ownership) - : IOObject(eFDTypeFile, false), m_descriptor(kInvalidDescriptor), - m_stream(fh), m_options(0), m_own_stream(transfer_ownership), + : IOObject(eFDTypeFile), m_descriptor(kInvalidDescriptor), + m_stream(fh), m_options(0), m_is_interactive(eLazyBoolCalculate), m_is_real_terminal(eLazyBoolCalculate), - m_supports_colors(eLazyBoolCalculate) {} + m_supports_colors(eLazyBoolCalculate), + m_fops(std::make_shared(fh, transfer_ownership)) {} File(int fd, bool transfer_ownership) - : IOObject(eFDTypeFile, transfer_ownership), m_descriptor(fd), - m_stream(kInvalidStream), m_options(0), m_own_stream(false), + : IOObject(eFDTypeFile), m_descriptor(fd), + m_stream(kInvalidStream), + m_options(0), m_is_interactive(eLazyBoolCalculate), + m_is_real_terminal(eLazyBoolCalculate), + m_fops(std::make_shared(fd, transfer_ownership)) {} + + File(const File &file) + : IOObject(eFDTypeFile), m_descriptor(file.m_descriptor), + m_stream(file.m_stream), m_options(file.m_options), + m_is_interactive(file.m_is_interactive), + m_is_real_terminal(file.m_is_real_terminal), + m_supports_colors(file.m_supports_colors), + m_fops(file.m_fops) {} + + File(std::shared_ptr fops) + : IOObject(eFDTypeFile), m_descriptor(kInvalidDescriptor), + m_stream(kInvalidStream), m_options(0), m_is_interactive(eLazyBoolCalculate), - m_is_real_terminal(eLazyBoolCalculate) {} + m_is_real_terminal(eLazyBoolCalculate), + m_supports_colors(eLazyBoolCalculate), + m_fops(fops) {} + + File(std::shared_ptr fops, int fd) + : IOObject(eFDTypeFile), m_descriptor(fd), + m_stream(kInvalidStream), m_options(0), + m_is_interactive(eLazyBoolCalculate), + m_is_real_terminal(eLazyBoolCalculate), + m_supports_colors(eLazyBoolCalculate), + m_fops(fops) {} /// Destructor. /// @@ -76,7 +157,7 @@ ~File() override; bool IsValid() const override { - return DescriptorIsValid() || StreamIsValid(); + return DescriptorIsValid() || StreamIsValid() || OverridesIO(); } /// Convert to pointer operator. @@ -93,7 +174,7 @@ /// \return /// A pointer to this object if either the directory or filename /// is valid, nullptr otherwise. - operator bool() const { return DescriptorIsValid() || StreamIsValid(); } + operator bool() const { return IsValid(); } /// Logical NOT operator. /// @@ -109,7 +190,7 @@ /// \return /// Returns \b true if the object has an empty directory and /// filename, \b false otherwise. - bool operator!() const { return !DescriptorIsValid() && !StreamIsValid(); } + bool operator!() const { return !IsValid(); } /// Get the file spec for this file. /// @@ -119,10 +200,24 @@ Status Close() override; - void Clear(); + /// DANGEROUS. Extract the underlying FILE* and reset this File without closing it. + /// + /// This is only here to support legacy SB interfaces that need to convert scripting + /// language objects into FILE* streams. That conversion is inherently sketchy and + /// doing so may cause the stream to be leaked. + /// + /// After calling this the File will be reset to it's original state. It will be + /// invalid and it will not hold on to any resources. + /// + /// \return + /// The underlying FILE* stream from this File, if one exists and can be extracted, + /// nullptr otherwise. + FILE *TakeStreamAndClear(); int GetDescriptor() const; + const char *GetFdopenMode() const; + WaitableHandle GetWaitableHandle() override; void SetDescriptor(int fd, bool transfer_ownership); @@ -131,6 +226,10 @@ void SetStream(FILE *fh, bool transfer_ownership); + void SetFile(const File &file); + + File &operator=(const File &file); + /// Read bytes from a file from the current file position. /// /// NOTE: This function is NOT thread safe. Use the read function @@ -341,20 +440,20 @@ bool StreamIsValid() const { return m_stream != kInvalidStream; } + bool OverridesIO() const { return m_fops && m_fops->m_overrides_io; } + void CalculateInteractiveAndTerminal(); // Member variables int m_descriptor; FILE *m_stream; uint32_t m_options; - bool m_own_stream; LazyBool m_is_interactive; LazyBool m_is_real_terminal; LazyBool m_supports_colors; std::mutex offset_access_mutex; + std::shared_ptr m_fops; -private: - DISALLOW_COPY_AND_ASSIGN(File); }; } // namespace lldb_private diff --git a/lldb/include/lldb/Host/Socket.h b/lldb/include/lldb/Host/Socket.h --- a/lldb/include/lldb/Host/Socket.h +++ b/lldb/include/lldb/Host/Socket.h @@ -122,6 +122,7 @@ SocketProtocol m_protocol; NativeSocket m_socket; bool m_child_processes_inherit; + bool m_should_close_fd; }; } // namespace lldb_private diff --git a/lldb/include/lldb/Interpreter/CommandReturnObject.h b/lldb/include/lldb/Interpreter/CommandReturnObject.h --- a/lldb/include/lldb/Interpreter/CommandReturnObject.h +++ b/lldb/include/lldb/Interpreter/CommandReturnObject.h @@ -62,13 +62,13 @@ return m_err_stream; } - void SetImmediateOutputFile(FILE *fh, bool transfer_fh_ownership = false) { - lldb::StreamSP stream_sp(new StreamFile(fh, transfer_fh_ownership)); + void SetImmediateOutputFile(File &file) { + lldb::StreamSP stream_sp(new StreamFile(file)); m_out_stream.SetStreamAtIndex(eImmediateStreamIndex, stream_sp); } - void SetImmediateErrorFile(FILE *fh, bool transfer_fh_ownership = false) { - lldb::StreamSP stream_sp(new StreamFile(fh, transfer_fh_ownership)); + void SetImmediateErrorFile(File &file) { + lldb::StreamSP stream_sp(new StreamFile(file)); m_err_stream.SetStreamAtIndex(eImmediateStreamIndex, stream_sp); } diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h --- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -463,8 +463,6 @@ static lldb::ScriptLanguage StringToLanguage(const llvm::StringRef &string); - virtual void ResetOutputFileHandle(FILE *new_fh) {} // By default, do nothing. - lldb::ScriptLanguage GetLanguage() { return m_script_lang; } protected: diff --git a/lldb/include/lldb/Utility/IOObject.h b/lldb/include/lldb/Utility/IOObject.h --- a/lldb/include/lldb/Utility/IOObject.h +++ b/lldb/include/lldb/Utility/IOObject.h @@ -29,8 +29,7 @@ typedef int WaitableHandle; static const WaitableHandle kInvalidHandleValue; - IOObject(FDType type, bool should_close) - : m_fd_type(type), m_should_close_fd(should_close) {} + IOObject(FDType type) : m_fd_type(type) {} virtual ~IOObject(); virtual Status Read(void *buf, size_t &num_bytes) = 0; @@ -44,8 +43,6 @@ protected: FDType m_fd_type; - bool m_should_close_fd; // True if this class should close the file descriptor - // when it goes away. private: DISALLOW_COPY_AND_ASSIGN(IOObject); diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h --- a/lldb/include/lldb/lldb-forward.h +++ b/lldb/include/lldb/lldb-forward.h @@ -332,6 +332,7 @@ ExecutionContextRefSP; typedef std::shared_ptr ExpressionVariableSP; typedef std::shared_ptr FileSP; +typedef std::unique_ptr FileUP; typedef std::shared_ptr FunctionSP; typedef std::shared_ptr FunctionCallerSP; typedef std::shared_ptr FuncUnwindersSP; 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 @@ -0,0 +1,626 @@ +""" +Test lldb Python API for setting output and error file handles +""" + +from __future__ import print_function + + +import contextlib +import os +import io +import re +import sys + +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * +from lldbsuite.test import lldbutil + + +class BadIO(io.TextIOBase): + def writable(self): + return True + def write(self, s): + raise Exception('OH NOE') + + +@contextlib.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: + line = line.strip() + if line: + yield line + return list(i()) + +def handle_command(debugger, cmd, raise_on_fail=True, collect_result=True): + + ret = lldb.SBCommandReturnObject() + + if collect_result: + interpreter = debugger.GetCommandInterpreter() + interpreter.HandleCommand(cmd, ret) + else: + debugger.HandleCommand(cmd) + + debugger.GetOutputFile().Flush() + debugger.GetErrorFile().Flush() + + if collect_result and raise_on_fail and not ret.Succeeded(): + raise Exception + + return ret.GetOutput() + + + +class FileHandleTestCase(TestBase): + + mydir = TestBase.compute_mydir(__file__) + + def comment(self, *args): + if self.session is not None: + print(*args, file=self.session) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_legacy_file_out(self): + + debugger = lldb.SBDebugger.Create() + try: + with open('output', 'w') as f: + debugger.SetOutputFileHandle(f, False) + handle_command(debugger, 'script 1+1') + debugger.GetOutputFileHandle().write('FOO\n') + + with open('output', 'r') as f: + self.assertEqual(readStrippedLines(f), ['2', 'FOO']) + + finally: + self.RemoveTempFile('output') + lldb.SBDebugger.Destroy(debugger) + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_fileno_out(self): + + debugger = lldb.SBDebugger.Create() + try: + with open('output', 'w') as f: + sbf = lldb.SBFile() + sbf.SetDescriptor(f.fileno(), False) + status = debugger.SetOutputFile(sbf) + if status.Fail(): + raise Exception(status) + handle_command(debugger, 'script 1+2') + + with open('output', 'r') as f: + self.assertEqual(f.read().strip(), '3') + + finally: + self.RemoveTempFile('output') + lldb.SBDebugger.Destroy(debugger) + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_file_out(self): + + debugger = lldb.SBDebugger.Create() + try: + with open('output', 'w') as f: + status = debugger.SetOutputFile(f) + if status.Fail(): + raise Exception(status) + handle_command(debugger, 'script 1+2') + + with open('output', 'r') as f: + self.assertEqual(f.read().strip(), '3') + + finally: + self.RemoveTempFile('output') + lldb.SBDebugger.Destroy(debugger) + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_fileno_help(self): + + debugger = lldb.SBDebugger.Create() + try: + with open('output', 'w') as f: + sbf = lldb.SBFile() + sbf.SetDescriptor(f.fileno(), False) + status = debugger.SetOutputFile(sbf) + if status.Fail(): + raise Exception(status) + handle_command(debugger, "help help", collect_result=False) + + with open('output', 'r') as f: + self.assertTrue(re.search(r'Show a list of all debugger commands', f.read())) + + finally: + self.RemoveTempFile('output') + lldb.SBDebugger.Destroy(debugger) + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_help(self): + + debugger = lldb.SBDebugger.Create() + try: + with open('output', 'w') as f: + status = debugger.SetOutputFile(f) + if status.Fail(): + raise Exception(status) + handle_command(debugger, "help help", collect_result=False) + + with open('output', 'r') as f: + self.assertTrue(re.search(r'Show a list of all debugger commands', f.read())) + + finally: + self.RemoveTempFile('output') + lldb.SBDebugger.Destroy(debugger) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_immediate(self): + + debugger = lldb.SBDebugger.Create() + try: + with open('output', 'w') as f: + ret = lldb.SBCommandReturnObject() + ret.SetImmediateOutputFile(f) + interpreter = debugger.GetCommandInterpreter() + interpreter.HandleCommand("help help", ret) + # make sure the file wasn't closed early. + f.write("\nQUUX\n") + + with open('output', '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)) + + finally: + self.RemoveTempFile('output') + lldb.SBDebugger.Destroy(debugger) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_close(self): + + debugger = lldb.SBDebugger.Create() + try: + with open('output', 'w') as f: + status = debugger.SetOutputFile(f) + if status.Fail(): + raise Exception(status) + handle_command(debugger, "help help", collect_result=False) + # make sure the file wasn't closed early. + f.write("\nZAP\n") + lldb.SBDebugger.Destroy(debugger) + # check that output file was closed when debugger was destroyed. + with self.assertRaises(ValueError): + f.write("\nQUUX\n") + + with open('output', 'r') as f: + output = f.read() + self.assertTrue(re.search(r'Show a list of all debugger commands', output)) + self.assertTrue(re.search(r'ZAP', output)) + + finally: + self.RemoveTempFile('output') + lldb.SBDebugger.Destroy(debugger) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_fileno_inout(self): + + debugger = lldb.SBDebugger.Create() + try: + with open('input', 'w') as f: + f.write("help help\n") + + with open('output', 'w') as outf, open('input', 'r') as inf: + + outsbf = lldb.SBFile() + outsbf.SetDescriptor(outf.fileno(), False) + status = debugger.SetOutputFile(outsbf) + if status.Fail(): + raise Exception(status) + + insbf = lldb.SBFile() + insbf.SetDescriptor(inf.fileno(), False) + status = debugger.SetInputFile(insbf) + if status.Fail(): + raise Exception(status) + + opts = lldb.SBCommandInterpreterRunOptions() + debugger.RunCommandInterpreter(True, False, opts, 0, False, False) + debugger.GetOutputFile().Flush() + + with open('output', 'r') as f: + self.assertTrue(re.search(r'Show a list of all debugger commands', f.read())) + + finally: + self.RemoveTempFile('output') + self.RemoveTempFile('input') + lldb.SBDebugger.Destroy(debugger) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_inout(self): + + debugger = lldb.SBDebugger.Create() + try: + with open('input', 'w') as f: + f.write("help help\n") + + with open('output', 'w') as outf, open('input', 'r') as inf: + + status = debugger.SetOutputFile(outf) + if status.Fail(): + raise Exception(status) + + status = debugger.SetInputFile(inf) + if status.Fail(): + raise Exception(status) + + opts = lldb.SBCommandInterpreterRunOptions() + debugger.RunCommandInterpreter(True, False, opts, 0, False, False) + debugger.GetOutputFile().Flush() + + with open('output', 'r') as f: + self.assertTrue(re.search(r'Show a list of all debugger commands', f.read())) + + finally: + self.RemoveTempFile('output') + self.RemoveTempFile('input') + lldb.SBDebugger.Destroy(debugger) + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_binary_inout(self): + + debugger = lldb.SBDebugger.Create() + try: + with open('input', 'w') as f: + f.write("help help\n") + + with open('output', 'wb') as outf, open('input', 'rb') as inf: + + status = debugger.SetOutputFile(outf) + if status.Fail(): + raise Exception(status) + + status = debugger.SetInputFile(inf) + if status.Fail(): + raise Exception(status) + + opts = lldb.SBCommandInterpreterRunOptions() + debugger.RunCommandInterpreter(True, False, opts, 0, False, False) + debugger.GetOutputFile().Flush() + + with open('output', 'r') as f: + self.assertTrue(re.search(r'Show a list of all debugger commands', f.read())) + + finally: + self.RemoveTempFile('output') + self.RemoveTempFile('input') + lldb.SBDebugger.Destroy(debugger) + + + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_fileno_error(self): + + debugger = lldb.SBDebugger.Create() + try: + with open('output', 'w') as f: + + sbf = lldb.SBFile() + sbf.SetDescriptor(f.fileno(), False) + status = debugger.SetErrorFile(sbf) + if status.Fail(): + raise Exception(status) + + handle_command(debugger, 'lolwut', raise_on_fail=False, collect_result=False) + + with open('output', 'r') as f: + errors = f.read() + self.assertTrue(re.search(r'error:.*lolwut', errors)) + + finally: + self.RemoveTempFile('output') + lldb.SBDebugger.Destroy(debugger) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_file_error(self): + + debugger = lldb.SBDebugger.Create() + try: + with open('output', 'w') as f: + + status = debugger.SetErrorFile(f) + if status.Fail(): + raise Exception(status) + + handle_command(debugger, 'lolwut', raise_on_fail=False, collect_result=False) + + with open('output', 'r') as f: + errors = f.read() + self.assertTrue(re.search(r'error:.*lolwut', errors)) + + finally: + self.RemoveTempFile('output') + lldb.SBDebugger.Destroy(debugger) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_legacy_file_error(self): + + debugger = lldb.SBDebugger.Create() + try: + with open('output', 'w') as f: + debugger.SetErrorFileHandle(f, False) + handle_command(debugger, 'lolwut', raise_on_fail=False, collect_result=False) + debugger.GetErrorFileHandle().write('FOOBAR\n') + + with open('output', 'r') as f: + errors = f.read() + self.assertTrue(re.search(r'error:.*lolwut', errors)) + self.assertTrue(re.search(r'FOOBAR', errors)) + + finally: + self.RemoveTempFile('output') + lldb.SBDebugger.Destroy(debugger) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + @skipIf(py_version=['<', (3,)]) + def test_string_out(self): + f = io.StringIO() + debugger = lldb.SBDebugger.Create() + try: + status = debugger.SetOutputFile(f) + if status.Fail(): + raise Exception(status) + handle_command(debugger, "script 'foobar'") + self.assertEqual(f.getvalue().strip(), "'foobar'") + finally: + lldb.SBDebugger.Destroy(debugger) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + @skipIf(py_version=['<', (3,)]) + def test_stdout(self): + f = io.StringIO() + debugger = lldb.SBDebugger.Create() + try: + status = debugger.SetOutputFile(f) + if status.Fail(): + raise Exception(status) + handle_command(debugger, r"script sys.stdout.write('foobar\n')") + self.assertEqual(f.getvalue().strip(), "foobar\n7") + finally: + lldb.SBDebugger.Destroy(debugger) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + @skipIf(py_version=['<', (3,)]) + def test_string_error(self): + + f = io.StringIO() + debugger = lldb.SBDebugger.Create() + try: + status = debugger.SetErrorFile(f) + if status.Fail(): + raise Exception + handle_command(debugger, 'lolwut', raise_on_fail=False, collect_result=False) + + errors = f.getvalue() + self.assertTrue(re.search(r'error:.*lolwut', errors)) + + finally: + lldb.SBDebugger.Destroy(debugger) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + @skipIf(py_version=['<', (3,)]) + def test_string_inout(self): + + debugger = lldb.SBDebugger.Create() + try: + + inf = io.StringIO("help help\nhelp b\n") + outf = io.StringIO() + + status = debugger.SetOutputFile(outf) + if status.Fail(): + raise Exception(status) + + status = debugger.SetInputFile(inf) + if status.Fail(): + raise Exception(status) + + opts = lldb.SBCommandInterpreterRunOptions() + debugger.RunCommandInterpreter(True, False, opts, 0, False, False) + debugger.GetOutputFile().Flush() + + self.assertTrue(re.search(r'Show a list of all debugger commands', outf.getvalue())) + self.assertTrue(re.search(r'Set a breakpoint', outf.getvalue())) + + finally: + lldb.SBDebugger.Destroy(debugger) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + @skipIf(py_version=['<', (3,)]) + def test_bytes_inout(self): + + debugger = lldb.SBDebugger.Create() + try: + + inf = io.BytesIO(b"help help\nhelp b\n") + outf = io.BytesIO() + + status = debugger.SetOutputFile(outf) + if status.Fail(): + raise Exception(status) + + status = debugger.SetInputFile(inf) + if status.Fail(): + raise Exception(status) + + opts = lldb.SBCommandInterpreterRunOptions() + debugger.RunCommandInterpreter(True, False, opts, 0, False, False) + debugger.GetOutputFile().Flush() + + self.assertTrue(re.search(b'Show a list of all debugger commands', outf.getvalue())) + self.assertTrue(re.search(b'Set a breakpoint', outf.getvalue())) + + finally: + lldb.SBDebugger.Destroy(debugger) + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_replace_stdout(self): + f = io.StringIO() + debugger = lldb.SBDebugger.Create() + try: + with replace_stdout(f): + self.assertEqual(sys.stdout, f) + handle_command(debugger, 'script sys.stdout.write("lol")', + collect_result=False) + self.assertEqual(sys.stdout, f) + finally: + lldb.SBDebugger.Destroy(debugger) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + def test_replace_stdout_with_nonfile(self): + + f = io.StringIO() + + with replace_stdout(f): + + class Nothing(): + pass + + debugger = lldb.SBDebugger.Create() + try: + with replace_stdout(Nothing): + self.assertEqual(sys.stdout, Nothing) + handle_command(debugger, 'script sys.stdout.write("lol")', + collect_result=False) + self.assertEqual(sys.stdout, Nothing) + finally: + lldb.SBDebugger.Destroy(debugger) + + sys.stdout.write(u"FOO") + + self.assertEqual(f.getvalue(), "FOO") + + + @add_test_categories(['pyapi']) + @no_debug_info_test + @skipIf(py_version=['<', (3,)]) + def test_stream_error(self): + + messages = list() + + io = BadIO() + debugger = lldb.SBDebugger.Create() + try: + debugger.SetOutputFile(io) + debugger.SetLoggingCallback(messages.append) + handle_command(debugger, 'log enable lldb script') + handle_command(debugger, 'script 1') + + finally: + lldb.SBDebugger.Destroy(debugger) + + self.comment(messages) + self.assertTrue(any('OH NOE' in msg for msg in messages)) + + + @add_test_categories(['pyapi']) + @no_debug_info_test + @skipIf(py_version=['<', (3,)]) + def test_identity(self): + f = io.StringIO() + sbf = lldb.SBFile() + sbf.SetFile(f) + self.assertTrue(f is sbf.GetFile()) + sbf.Close() + self.assertTrue(f.closed) + + f = io.StringIO() + sbf.SetFileBorrowed(f) + self.assertTrue(f is sbf.GetFile()) + sbf.Close() + self.assertFalse(f.closed) + + try: + with open('output', 'w') as f: + sbf.SetFile(f) + self.assertTrue(f is sbf.GetFile()) + sbf.Close() + self.assertTrue(f.closed) + + with open('output', 'w') as f: + sbf.SetFileBorrowed(f) + self.assertFalse(f is sbf.GetFile()) + sbf.Write(b"foobar\n") + self.assertEqual(f.fileno(), sbf.GetFile().fileno()) + sbf.Close() + self.assertFalse(f.closed) + + with open('output', 'r') as f: + self.assertEqual("foobar", f.read().strip()) + + with open('output', 'wb') as f: + sbf.SetFileBorrowedForcingUseOfScriptingIOMethods(f) + self.assertTrue(f is sbf.GetFile()) + sbf.Write(b"foobar\n") + self.assertEqual(f.fileno(), sbf.GetFile().fileno()) + sbf.Close() + self.assertFalse(f.closed) + + with open('output', 'r') as f: + self.assertEqual("foobar", f.read().strip()) + + with open('output', 'wb') as f: + sbf.SetFileForcingUseOfScriptingIOMethods(f) + self.assertTrue(f is sbf.GetFile()) + sbf.Write(b"foobar\n") + self.assertEqual(f.fileno(), sbf.GetFile().fileno()) + sbf.Close() + self.assertTrue(f.closed) + + with open('output', 'r') as f: + self.assertEqual("foobar", f.read().strip()) + + + finally: + self.RemoveTempFile('output') diff --git a/lldb/scripts/Python/prepare_binding_Python.py b/lldb/scripts/Python/prepare_binding_Python.py --- a/lldb/scripts/Python/prepare_binding_Python.py +++ b/lldb/scripts/Python/prepare_binding_Python.py @@ -231,11 +231,13 @@ swig_stdout, swig_stderr = process.communicate() return_code = process.returncode if return_code != 0: + swig_stdout = swig_stdout.decode('utf8', errors='replace').rstrip() + swig_stderr = swig_stderr.decode('utf8', errors='replace').rstrip() + swig_stdout = re.sub(r'^(?=.)', 'stdout: ', swig_stdout, flags=re.MULTILINE) + swig_stderr = re.sub(r'^(?=.)', 'stderr: ', swig_stderr, flags=re.MULTILINE) logging.error( - "swig failed with error code %d: stdout=%s, stderr=%s", - return_code, - swig_stdout, - swig_stderr) + "swig failed with error code %d\n%s%s", + return_code, swig_stdout, swig_stderr) logging.error( "command line:\n%s", ' '.join(command)) sys.exit(return_code) diff --git a/lldb/scripts/Python/python-typemaps.swig b/lldb/scripts/Python/python-typemaps.swig --- a/lldb/scripts/Python/python-typemaps.swig +++ b/lldb/scripts/Python/python-typemaps.swig @@ -372,64 +372,71 @@ $1 = $1 || PyCallable_Check(reinterpret_cast($input)); } -%typemap(in) FILE * { - using namespace lldb_private; - if ($input == Py_None) - $1 = nullptr; - else if (!lldb_private::PythonFile::Check($input)) { - int fd = PyObject_AsFileDescriptor($input); - PythonObject py_input(PyRefType::Borrowed, $input); - PythonString py_mode = py_input.GetAttributeValue("mode").AsType(); - - if (-1 != fd && py_mode.IsValid()) { - FILE *f; - if ((f = fdopen(fd, py_mode.GetString().str().c_str()))) - $1 = f; - else - PyErr_SetString(PyExc_TypeError, strerror(errno)); - } else { - PyErr_SetString(PyExc_TypeError,"not a file-like object"); - return nullptr; - } - } - else - { - PythonFile py_file(PyRefType::Borrowed, $input); - File file; - if (!py_file.GetUnderlyingFile(file)) - return nullptr; - - $1 = file.GetStream(); - if ($1) - file.Clear(); - } +%typemap(in) lldb_private::File & (lldb_private::File lldb_file) { + using namespace lldb_private; + if (PythonFile::Check($input)) { + PythonFile py_file(PyRefType::Borrowed, $input); + py_file.ConvertToFile(lldb_file); + } + $1 = &lldb_file; } -%typemap(out) FILE * { - char mode[4] = {0}; -%#ifdef __APPLE__ - int i = 0; - if ($1) - { - short flags = $1->_flags; - - if (flags & __SRD) - mode[i++] = 'r'; - else if (flags & __SWR) - mode[i++] = 'w'; - else // if (flags & __SRW) - mode[i++] = 'a'; - } -%#endif - using namespace lldb_private; - File file($1, false); - PythonFile py_file(file, mode); - $result = py_file.release(); - if (!$result) - { - $result = Py_None; - Py_INCREF(Py_None); - } +%typemap(in) lldb_private::File &FORCE_IO_METHODS (lldb_private::File lldb_file) { + using namespace lldb_private; + if (PythonFile::Check($input)) { + PythonFile py_file(PyRefType::Borrowed, $input); + py_file.ConvertToFileForcingUseOfScriptingIOMethods(lldb_file); + } + $1 = &lldb_file; +} + +%typemap(in) lldb_private::File &BORROWED (lldb_private::File lldb_file) { + using namespace lldb_private; + if (PythonFile::Check($input)) { + PythonFile py_file(PyRefType::Borrowed, $input); + py_file.ConvertToFile(lldb_file, /*borrowed=*/true); + } + $1 = &lldb_file; +} + +%typemap(in) lldb_private::File &BORROWED_FORCE_IO_METHODS (lldb_private::File lldb_file) { + using namespace lldb_private; + if (PythonFile::Check($input)) { + PythonFile py_file(PyRefType::Borrowed, $input); + py_file.ConvertToFileForcingUseOfScriptingIOMethods(lldb_file, /*borrowed=*/true); + } + $1 = &lldb_file; +} + +%typecheck(SWIG_TYPECHECK_POINTER) lldb_private::File & { + if (lldb_private::PythonFile::Check($input)) { + $1 = 1; + } else { + PyErr_Clear(); + $1 = 0; + } +} + +%typemap(out) lldb_private::File & { + using namespace lldb_private; + PythonFile pyfile(*$1); + $result = pyfile.release(); + if (!$result) + { + $result = Py_None; + Py_INCREF(Py_None); + } +} + +%typemap(out) lldb_private::File { + using namespace lldb_private; + PythonFile pyfile($1); + $result = pyfile.release(); + if (!$result) + { + $result = Py_None; + Py_INCREF(Py_None); + } } %typemap(in) (const char* string, int len) { diff --git a/lldb/scripts/interface/SBCommandReturnObject.i b/lldb/scripts/interface/SBCommandReturnObject.i --- a/lldb/scripts/interface/SBCommandReturnObject.i +++ b/lldb/scripts/interface/SBCommandReturnObject.i @@ -49,10 +49,16 @@ GetError (bool if_no_immediate); size_t - PutOutput (FILE *fh); + PutOutput (SBFile &file); size_t - PutError (FILE *fh); + PutError (SBFile &file); + + size_t + PutOutput (lldb_private::File &BORROWED); + + size_t + PutError (lldb_private::File &BORROWED); void Clear(); @@ -85,15 +91,20 @@ bool GetDescription (lldb::SBStream &description); + void SetImmediateOutputFile(SBFile &file); + void SetImmediateErrorFile(SBFile &file); + void SetImmediateOutputFile(lldb_private::File &BORROWED); + void SetImmediateErrorFile(lldb_private::File &BORROWED); - // wrapping here so that lldb takes ownership of the - // new FILE* created inside of the swig interface %extend { - void SetImmediateOutputFile(FILE *fh) { - self->SetImmediateOutputFile(fh, true); + // transfer_ownership does nothing, and is here for compatibility with + // old scripts. Ownership is tracked by reference count in the ordinary way. + + void SetImmediateOutputFile(lldb_private::File &BORROWED, bool transfer_ownership) { + self->SetImmediateOutputFile(BORROWED); } - void SetImmediateErrorFile(FILE *fh) { - self->SetImmediateErrorFile(fh, true); + void SetImmediateErrorFile(lldb_private::File &BORROWED, bool transfer_ownership) { + self->SetImmediateErrorFile(BORROWED); } } 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 @@ -165,23 +165,68 @@ void SkipLLDBInitFiles (bool b); - void - SetInputFileHandle (FILE *f, bool transfer_ownership); + %extend { - void - SetOutputFileHandle (FILE *f, bool transfer_ownership); + %feature("autodoc", "DEPRECATED, use SetInputFile"); + void + SetInputFileHandle (lldb_private::File &file, + bool transfer_ownership) { + self->SetInputFile(file); + } - void - SetErrorFileHandle (FILE *f, bool transfer_ownership); + %feature("autodoc", "DEPRECATED, use SetOutputFile"); + void + SetOutputFileHandle (lldb_private::File &file, + bool transfer_ownership) { + self->SetOutputFile(file); + } + + %feature("autodoc", "DEPRECATED, use SetErrorFile"); + void + SetErrorFileHandle (lldb_private::File &file, + bool transfer_ownership) { + self->SetErrorFile(file); + } + + lldb_private::File GetInputFileHandle() { + return self->GetInputFile().GetFile(); + } + + lldb_private::File GetOutputFileHandle() { + return self->GetOutputFile().GetFile(); + } + + lldb_private::File GetErrorFileHandle() { + return self->GetErrorFile().GetFile(); + } + } + + SBError + SetInputFile (SBFile &file); + + SBError + SetOutputFile (SBFile &file); - FILE * - GetInputFileHandle (); + SBError + SetErrorFile (SBFile &file); - FILE * - GetOutputFileHandle (); + SBError + SetInputFile (lldb_private::File &file); - FILE * - GetErrorFileHandle (); + SBError + SetOutputFile (lldb_private::File &file); + + SBError + SetErrorFile (lldb_private::File &file); + + SBFile + GetInputFile (); + + SBFile + GetOutputFile (); + + SBFile + GetErrorFile (); lldb::SBCommandInterpreter GetCommandInterpreter (); @@ -195,8 +240,14 @@ void HandleProcessEvent (const lldb::SBProcess &process, const lldb::SBEvent &event, - FILE *out, - FILE *err); + SBFile &out, + SBFile &err); + + void + HandleProcessEvent (const lldb::SBProcess &process, + const lldb::SBEvent &event, + lldb_private::File &BORROWED, + lldb_private::File &BORROWED); lldb::SBTarget CreateTarget (const char *filename, diff --git a/lldb/scripts/interface/SBFile.i b/lldb/scripts/interface/SBFile.i new file mode 100644 --- /dev/null +++ b/lldb/scripts/interface/SBFile.i @@ -0,0 +1,65 @@ +//===-- SWIG Interface for SBFile -----------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +%include + +%pybuffer_binary(const uint8_t *buf, size_t num_bytes); +%pybuffer_mutable_binary(uint8_t *buf, size_t num_bytes); + +namespace lldb { + +%feature("docstring", +"Represents a file." +) SBFile; + +class SBFile +{ +public: + + SBFile(); + ~SBFile (); + + void SetStream (FILE *file, bool transfer_ownership); + void SetDescriptor (int fd, bool transfer_ownership); + + void SetFile(lldb_private::File &file); + + %extend { + %feature("docstring", "Like SetFile(), but the underlying file will not be closed when the SBFile is closed or destroyed."); + void SetFileBorrowed(lldb_private::File &BORROWED) { + self->SetFile(BORROWED); + } + + %feature("docstring" "like SetFile(), but the python read/write methods will be called even if a file descriptor is available."); + void SetFileForcingUseOfScriptingIOMethods(lldb_private::File &FORCE_IO_METHODS) + { + self->SetFile(FORCE_IO_METHODS); + } + + void SetFileBorrowedForcingUseOfScriptingIOMethods(lldb_private::File &BORROWED_FORCE_IO_METHODS) + { + self->SetFile(BORROWED_FORCE_IO_METHODS); + } + } + + %feature("autodoc", "Read(buffer) -> SBError, bytes_read") Read; + SBError Read(uint8_t *buf, size_t num_bytes, size_t *OUTPUT); + + %feature("autodoc", "Write(buffer) -> SBError, written_read") Write; + SBError Write(const uint8_t *buf, size_t num_bytes, size_t *OUTPUT); + + void Flush(); + + bool IsValid() const; + + SBError Close(); + + lldb_private::File &GetFile(); +}; + +} // namespace lldb diff --git a/lldb/scripts/interface/SBInstruction.i b/lldb/scripts/interface/SBInstruction.i --- a/lldb/scripts/interface/SBInstruction.i +++ b/lldb/scripts/interface/SBInstruction.i @@ -57,7 +57,10 @@ CanSetBreakpoint (); void - Print (FILE *out); + Print (SBFile &out); + + void + Print (lldb_private::File &BORROWED); bool GetDescription (lldb::SBStream &description); diff --git a/lldb/scripts/interface/SBInstructionList.i b/lldb/scripts/interface/SBInstructionList.i --- a/lldb/scripts/interface/SBInstructionList.i +++ b/lldb/scripts/interface/SBInstructionList.i @@ -55,7 +55,10 @@ AppendInstruction (lldb::SBInstruction inst); void - Print (FILE *out); + Print (SBFile &out); + + void + Print (lldb_private::File &BORROWED); bool GetDescription (lldb::SBStream &description); diff --git a/lldb/scripts/interface/SBProcess.i b/lldb/scripts/interface/SBProcess.i --- a/lldb/scripts/interface/SBProcess.i +++ b/lldb/scripts/interface/SBProcess.i @@ -97,7 +97,10 @@ GetAsyncProfileData(char *dst, size_t dst_len) const; void - ReportEventState (const lldb::SBEvent &event, FILE *out) const; + ReportEventState (const lldb::SBEvent &event, SBFile &out) const; + + void + ReportEventState (const lldb::SBEvent &event, lldb_private::File &BORROWED) const; void AppendEventStateReport (const lldb::SBEvent &event, lldb::SBCommandReturnObject &result); diff --git a/lldb/scripts/interface/SBStream.i b/lldb/scripts/interface/SBStream.i --- a/lldb/scripts/interface/SBStream.i +++ b/lldb/scripts/interface/SBStream.i @@ -75,7 +75,18 @@ RedirectToFile (const char *path, bool append); void - RedirectToFileHandle (FILE *fh, bool transfer_fh_ownership); + RedirectToFile (SBFile &file); + + void + RedirectToFile (lldb_private::File &file); + + %extend { + %feature("autodoc", "DEPRECATED, use RedirectToFile"); + void + RedirectToFileHandle (lldb_private::File &file, bool transfer_fh_ownership) { + self->RedirectToFile(file); + } + } void RedirectToFileDescriptor (int fd, bool transfer_fh_ownership); diff --git a/lldb/scripts/lldb.swig b/lldb/scripts/lldb.swig --- a/lldb/scripts/lldb.swig +++ b/lldb/scripts/lldb.swig @@ -123,6 +123,7 @@ #include "lldb/API/SBExecutionContext.h" #include "lldb/API/SBExpressionOptions.h" #include "lldb/API/SBFileSpec.h" +#include "lldb/API/SBFile.h" #include "lldb/API/SBFileSpecList.h" #include "lldb/API/SBFrame.h" #include "lldb/API/SBFunction.h" @@ -210,6 +211,7 @@ %include "./interface/SBExecutionContext.i" %include "./interface/SBExpressionOptions.i" %include "./interface/SBFileSpec.i" +%include "./interface/SBFile.i" %include "./interface/SBFileSpecList.i" %include "./interface/SBFrame.i" %include "./interface/SBFunction.i" diff --git a/lldb/source/API/CMakeLists.txt b/lldb/source/API/CMakeLists.txt --- a/lldb/source/API/CMakeLists.txt +++ b/lldb/source/API/CMakeLists.txt @@ -34,6 +34,7 @@ SBExecutionContext.cpp SBExpressionOptions.cpp SBFileSpec.cpp + SBFile.cpp SBFileSpecList.cpp SBFrame.cpp SBFunction.cpp diff --git a/lldb/source/API/SBCommandReturnObject.cpp b/lldb/source/API/SBCommandReturnObject.cpp --- a/lldb/source/API/SBCommandReturnObject.cpp +++ b/lldb/source/API/SBCommandReturnObject.cpp @@ -10,6 +10,7 @@ #include "SBReproducerPrivate.h" #include "Utils.h" #include "lldb/API/SBError.h" +#include "lldb/API/SBFile.h" #include "lldb/API/SBStream.h" #include "lldb/Interpreter/CommandReturnObject.h" #include "lldb/Utility/ConstString.h" @@ -104,7 +105,6 @@ size_t SBCommandReturnObject::PutOutput(FILE *fh) { LLDB_RECORD_METHOD(size_t, SBCommandReturnObject, PutOutput, (FILE *), fh); - if (fh) { size_t num_bytes = GetOutputSize(); if (num_bytes) @@ -113,6 +113,17 @@ return 0; } +size_t SBCommandReturnObject::PutOutput(File &file) { + LLDB_RECORD_METHOD(size_t, SBCommandReturnObject, PutOutput, (File &), file); + return file.Printf("%s", GetOutput()); +} + +size_t SBCommandReturnObject::PutOutput(SBFile &file) { + LLDB_RECORD_METHOD(size_t, SBCommandReturnObject, PutOutput, (SBFile &), file); + return file.GetFile().Printf("%s", GetOutput()); +} + + size_t SBCommandReturnObject::PutError(FILE *fh) { LLDB_RECORD_METHOD(size_t, SBCommandReturnObject, PutError, (FILE *), fh); @@ -124,6 +135,16 @@ return 0; } +size_t SBCommandReturnObject::PutError(File &file) { + LLDB_RECORD_METHOD(size_t, SBCommandReturnObject, PutError, (File &), file); + return file.Printf("%s", GetError()); +} + +size_t SBCommandReturnObject::PutError(SBFile &file) { + LLDB_RECORD_METHOD(size_t, SBCommandReturnObject, PutError, (SBFile &), file); + return file.GetFile().Printf("%s", GetError()); +} + void SBCommandReturnObject::Clear() { LLDB_RECORD_METHOD_NO_ARGS(void, SBCommandReturnObject, Clear); @@ -245,8 +266,10 @@ LLDB_RECORD_METHOD(void, SBCommandReturnObject, SetImmediateOutputFile, (FILE *, bool), fh, transfer_ownership); - if (m_opaque_up) - m_opaque_up->SetImmediateOutputFile(fh, transfer_ownership); + if (m_opaque_up) { + File file(fh, transfer_ownership); + m_opaque_up->SetImmediateOutputFile(file); + } } void SBCommandReturnObject::SetImmediateErrorFile(FILE *fh, @@ -254,8 +277,42 @@ LLDB_RECORD_METHOD(void, SBCommandReturnObject, SetImmediateErrorFile, (FILE *, bool), fh, transfer_ownership); - if (m_opaque_up) - m_opaque_up->SetImmediateErrorFile(fh, transfer_ownership); + if (m_opaque_up) { + File file(fh, transfer_ownership); + m_opaque_up->SetImmediateErrorFile(file); + } +} + +void SBCommandReturnObject::SetImmediateOutputFile(SBFile &file) { + LLDB_RECORD_METHOD(void, SBCommandReturnObject, SetImmediateOutputFile, + (SBFile &), file); + if (m_opaque_up) { + m_opaque_up->SetImmediateOutputFile(file.GetFile()); + } +} + +void SBCommandReturnObject::SetImmediateErrorFile(SBFile &file) { + LLDB_RECORD_METHOD(void, SBCommandReturnObject, SetImmediateErrorFile, + (SBFile &), file); + if (m_opaque_up) { + m_opaque_up->SetImmediateErrorFile(file.GetFile()); + } +} + +void SBCommandReturnObject::SetImmediateOutputFile(File &file) { + LLDB_RECORD_METHOD(void, SBCommandReturnObject, SetImmediateOutputFile, + (File &), file); + if (m_opaque_up) { + m_opaque_up->SetImmediateOutputFile(file); + } +} + +void SBCommandReturnObject::SetImmediateErrorFile(File &file) { + LLDB_RECORD_METHOD(void, SBCommandReturnObject, SetImmediateErrorFile, + (File &), file); + if (m_opaque_up) { + m_opaque_up->SetImmediateErrorFile(file); + } } void SBCommandReturnObject::PutCString(const char *string, int len) { @@ -353,6 +410,10 @@ LLDB_REGISTER_METHOD(size_t, SBCommandReturnObject, GetErrorSize, ()); LLDB_REGISTER_METHOD(size_t, SBCommandReturnObject, PutOutput, (FILE *)); LLDB_REGISTER_METHOD(size_t, SBCommandReturnObject, PutError, (FILE *)); + LLDB_REGISTER_METHOD(size_t, SBCommandReturnObject, PutOutput, (SBFile &)); + LLDB_REGISTER_METHOD(size_t, SBCommandReturnObject, PutError, (SBFile &)); + LLDB_REGISTER_METHOD(size_t, SBCommandReturnObject, PutOutput, (File &)); + LLDB_REGISTER_METHOD(size_t, SBCommandReturnObject, PutError, (File &)); LLDB_REGISTER_METHOD(void, SBCommandReturnObject, Clear, ()); LLDB_REGISTER_METHOD(lldb::ReturnStatus, SBCommandReturnObject, GetStatus, ()); @@ -370,6 +431,10 @@ (FILE *)); LLDB_REGISTER_METHOD(void, SBCommandReturnObject, SetImmediateErrorFile, (FILE *)); + LLDB_REGISTER_METHOD(void, SBCommandReturnObject, SetImmediateOutputFile, (SBFile &)); + LLDB_REGISTER_METHOD(void, SBCommandReturnObject, SetImmediateErrorFile, (SBFile &)); + LLDB_REGISTER_METHOD(void, SBCommandReturnObject, SetImmediateOutputFile, (File &)); + LLDB_REGISTER_METHOD(void, SBCommandReturnObject, SetImmediateErrorFile, (File &)); LLDB_REGISTER_METHOD(void, SBCommandReturnObject, SetImmediateOutputFile, (FILE *, bool)); LLDB_REGISTER_METHOD(void, SBCommandReturnObject, SetImmediateErrorFile, 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 @@ -33,6 +33,7 @@ #include "lldb/API/SBTypeNameSpecifier.h" #include "lldb/API/SBTypeSummary.h" #include "lldb/API/SBTypeSynthetic.h" +#include "lldb/API/SBFile.h" #include "lldb/Core/Debugger.h" #include "lldb/Core/PluginManager.h" @@ -304,25 +305,109 @@ 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); + File file(fh, transfer_ownership); + m_opaque_sp->SetInputFile(file, recorder); +} + +SBError SBDebugger::SetInputFile(SBFile &file) { + LLDB_RECORD_METHOD(SBError, SBDebugger, SetInputFile, (SBFile &), file); + return SetInputFile(file.GetFile()); +} + +SBError SBDebugger::SetInputFile(File &file) { + LLDB_RECORD_METHOD(SBError, SBDebugger, SetInputFile, (File &), 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(); + + static std::unique_ptr loader = + repro::CommandLoader::Create(repro::Reproducer::Instance().GetLoader()); + File loader_file; + if (loader) { + llvm::Optional nextfile = loader->GetNextFile(); + FILE *fh = nextfile ? FileSystem::Instance().Fopen(nextfile->c_str(), "r") : nullptr; + if (fh) { + loader_file.SetStream(fh, true); + } + } + + if (loader_file) { + error.SetError(m_opaque_sp->SetInputFile(loader_file, recorder)); + } else if (file) { + error.SetError(m_opaque_sp->SetInputFile(file, 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) { + File file(fh, transfer_ownership); + m_opaque_sp->SetOutputFile(file); + } +} + +SBError SBDebugger::SetOutputFile(SBFile &file) { + LLDB_RECORD_METHOD(SBError, SBDebugger, SetOutputFile, (SBFile &file), file); + return SetOutputFile(file.GetFile()); +} + +SBError SBDebugger::SetOutputFile(File &file) { + LLDB_RECORD_METHOD(SBError, SBDebugger, SetOutputFile, (File &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)); + 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->SetErrorFileHandle(fh, transfer_ownership); + if (m_opaque_sp) { + File file(fh, transfer_ownership); + m_opaque_sp->SetErrorFile(file); + } +} + +SBError SBDebugger::SetErrorFile(SBFile &file) { + LLDB_RECORD_METHOD(SBError, SBDebugger, SetErrorFile, (SBFile &file), file); + return SetErrorFile(file.GetFile()); +} + +SBError SBDebugger::SetErrorFile(File &file) { + LLDB_RECORD_METHOD(SBError, SBDebugger, SetErrorFile, (File &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)); + return error; } FILE *SBDebugger::GetInputFileHandle() { @@ -336,6 +421,18 @@ return nullptr; } +SBFile SBDebugger::GetInputFile() { + LLDB_RECORD_METHOD_NO_ARGS(SBFile, SBDebugger, GetInputFile); + SBFile file; + if (m_opaque_sp) { + StreamFileSP stream_file_sp(m_opaque_sp->GetInputFile()); + if (stream_file_sp) { + file.SetFile(stream_file_sp->GetFile()); + } + } + return LLDB_RECORD_RESULT(file); +} + FILE *SBDebugger::GetOutputFileHandle() { LLDB_RECORD_METHOD_NO_ARGS(FILE *, SBDebugger, GetOutputFileHandle); @@ -347,6 +444,18 @@ return nullptr; } +SBFile SBDebugger::GetOutputFile() { + LLDB_RECORD_METHOD_NO_ARGS(SBFile, SBDebugger, GetOutputFile); + SBFile file; + if (m_opaque_sp) { + StreamFileSP stream_file_sp(m_opaque_sp->GetOutputFile()); + if (stream_file_sp) { + file.SetFile(stream_file_sp->GetFile()); + } + } + return LLDB_RECORD_RESULT(file); +} + FILE *SBDebugger::GetErrorFileHandle() { LLDB_RECORD_METHOD_NO_ARGS(FILE *, SBDebugger, GetErrorFileHandle); @@ -358,6 +467,18 @@ return nullptr; } +SBFile SBDebugger::GetErrorFile() { + LLDB_RECORD_METHOD_NO_ARGS(SBFile, SBDebugger, GetErrorFile); + SBFile file; + if (m_opaque_sp) { + StreamFileSP stream_file_sp(m_opaque_sp->GetErrorFile()); + if (stream_file_sp) { + file.SetFile(stream_file_sp->GetFile()); + } + } + return LLDB_RECORD_RESULT(file); +} + void SBDebugger::SaveInputTerminalState() { LLDB_RECORD_METHOD_NO_ARGS(void, SBDebugger, SaveInputTerminalState); @@ -396,10 +517,12 @@ sb_interpreter.HandleCommand(command, result, false); - if (GetErrorFileHandle() != nullptr) - result.PutError(GetErrorFileHandle()); - if (GetOutputFileHandle() != nullptr) - result.PutOutput(GetOutputFileHandle()); + auto error_file = m_opaque_sp->GetErrorFile(); + auto output_file = m_opaque_sp->GetOutputFile(); + if (error_file) + result.PutError(error_file->GetFile()); + if (output_file) + result.PutOutput(output_file->GetFile()); if (!m_opaque_sp->GetAsyncExecution()) { SBProcess process(GetCommandInterpreter().GetProcess()); @@ -410,8 +533,7 @@ while (lldb_listener_sp->GetEventForBroadcaster( process_sp.get(), event_sp, std::chrono::seconds(0))) { SBEvent event(event_sp); - HandleProcessEvent(process, event, GetOutputFileHandle(), - GetErrorFileHandle()); + HandleProcessEvent(process, event, GetOutputFile(), GetErrorFile()); } } } @@ -428,6 +550,17 @@ return LLDB_RECORD_RESULT(sb_listener); } +void SBDebugger::HandleProcessEvent(const SBProcess &process, const SBEvent &event, + SBFile &out, SBFile &err) { + + LLDB_RECORD_METHOD( + void, SBDebugger, HandleProcessEvent, + (const lldb::SBProcess &, const lldb::SBEvent &, SBFile &, SBFile &), process, + event, out, err); + + return HandleProcessEvent(process, event, out.GetFile(), err.GetFile()); +} + void SBDebugger::HandleProcessEvent(const SBProcess &process, const SBEvent &event, FILE *out, FILE *err) { @@ -436,6 +569,19 @@ (const lldb::SBProcess &, const lldb::SBEvent &, FILE *, FILE *), process, event, out, err); + File outfile(out, false); + File errfile(err, false); + return HandleProcessEvent(process, event, outfile, errfile); +} + +void SBDebugger::HandleProcessEvent(const SBProcess &process, const SBEvent &event, + File &out, File &err) { + + LLDB_RECORD_METHOD( + void, SBDebugger, HandleProcessEvent, + (const lldb::SBProcess &, const lldb::SBEvent &, File &, File &), process, + event, out, err); + if (!process.IsValid()) return; @@ -453,16 +599,14 @@ (Process::eBroadcastBitSTDOUT | Process::eBroadcastBitStateChanged)) { // Drain stdout when we stop just in case we have any bytes while ((len = process.GetSTDOUT(stdio_buffer, sizeof(stdio_buffer))) > 0) - if (out != nullptr) - ::fwrite(stdio_buffer, 1, len, out); + out.Write(stdio_buffer, len); } if (event_type & (Process::eBroadcastBitSTDERR | Process::eBroadcastBitStateChanged)) { // Drain stderr when we stop just in case we have any bytes while ((len = process.GetSTDERR(stdio_buffer, sizeof(stdio_buffer))) > 0) - if (err != nullptr) - ::fwrite(stdio_buffer, 1, len, err); + err.Write(stdio_buffer, len); } if (event_type & Process::eBroadcastBitStateChanged) { @@ -1506,6 +1650,14 @@ // Do nothing. } +static SBError SetFileRedirect(SBDebugger *, File &file) { + return SBError(); +} + +static SBError SetSBFileRedirect(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 @@ -1526,6 +1678,26 @@ &SBDebugger::GetDefaultArchitecture), &GetDefaultArchitectureRedirect); + R.Register( + &invoke::method<&SBDebugger::SetInputFile>::doit, + &SetSBFileRedirect); + R.Register( + &invoke::method<&SBDebugger::SetOutputFile>::doit, + &SetSBFileRedirect); + R.Register( + &invoke::method<&SBDebugger::SetErrorFile>::doit, + &SetSBFileRedirect); + + 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 &)); @@ -1550,6 +1722,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, @@ -1559,6 +1734,12 @@ LLDB_REGISTER_METHOD( void, SBDebugger, HandleProcessEvent, (const lldb::SBProcess &, const lldb::SBEvent &, FILE *, FILE *)); + LLDB_REGISTER_METHOD( + void, SBDebugger, HandleProcessEvent, + (const lldb::SBProcess &, const lldb::SBEvent &, SBFile &, SBFile &)); + LLDB_REGISTER_METHOD( + void, SBDebugger, HandleProcessEvent, + (const lldb::SBProcess &, const lldb::SBEvent &, File &, File &)); LLDB_REGISTER_METHOD(lldb::SBSourceManager, SBDebugger, GetSourceManager, ()); LLDB_REGISTER_STATIC_METHOD(bool, SBDebugger, SetDefaultArchitecture, (const char *)); diff --git a/lldb/source/API/SBFile.cpp b/lldb/source/API/SBFile.cpp new file mode 100644 --- /dev/null +++ b/lldb/source/API/SBFile.cpp @@ -0,0 +1,103 @@ +//===-- SBFile.cpp ------------------------------------------*- C++ -*-===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// + +#include "lldb/API/SBFile.h" +#include "lldb/API/SBError.h" +#include "lldb/Host/File.h" + +using namespace lldb; +using namespace lldb_private; + +SBFile::~SBFile() {} + +SBFile::SBFile () {} + +SBFile::SBFile(const SBFile &file) { + m_opaque_up = std::make_unique(file.GetFile()); +} + +SBFile::SBFile(SBFile &&file) { + std::swap(m_opaque_up, file.m_opaque_up); +} + +SBFile &SBFile::operator= (const SBFile &file) { + if (m_opaque_up) { + m_opaque_up->SetFile(file.GetFile()); + } else { + m_opaque_up = std::make_unique(file.GetFile()); + } + return *this; +} + +void SBFile::SetFile(lldb_private::File &file) { + if (m_opaque_up) { + m_opaque_up->SetFile(file); + } else { + m_opaque_up = std::make_unique(file); + } +} + +void SBFile::SetStream(FILE *file, bool transfer_ownership) { + m_opaque_up = std::make_unique(file, transfer_ownership); +} + +void SBFile::SetDescriptor(int fd, bool transfer_owndership) { + m_opaque_up = std::make_unique(fd, transfer_owndership); +} + +SBError SBFile::Read(uint8_t *buf, size_t num_bytes, size_t *bytes_read) { + SBError error; + if (!m_opaque_up) { + error.SetErrorString("invalid SBFile"); + *bytes_read = 0; + } else { + Status status = m_opaque_up->Read(buf, num_bytes); + error.SetError(status); + *bytes_read = num_bytes; + } + return error; +} + +SBError SBFile::Write(const uint8_t *buf, size_t num_bytes, size_t *bytes_written) { + SBError error; + if (!m_opaque_up) { + error.SetErrorString("invalid SBFile"); + *bytes_written = 0; + } else { + Status status = m_opaque_up->Write(buf, num_bytes); + error.SetError(status); + *bytes_written = num_bytes; + } + return error; +} + +SBError SBFile::Flush() { + SBError error; + if (!m_opaque_up) { + error.SetErrorString("invalid SBFile"); + } else { + Status status = m_opaque_up->Flush(); + error.SetError(status); + } + return error; +} + +bool SBFile::IsValid() const { + return m_opaque_up && m_opaque_up->IsValid(); +} + +SBError SBFile::Close() { + SBError error; + if (m_opaque_up) { + Status status = m_opaque_up->Close(); + error.SetError(status); + } + return error; +} + + diff --git a/lldb/source/API/SBInstruction.cpp b/lldb/source/API/SBInstruction.cpp --- a/lldb/source/API/SBInstruction.cpp +++ b/lldb/source/API/SBInstruction.cpp @@ -11,6 +11,7 @@ #include "lldb/API/SBAddress.h" #include "lldb/API/SBFrame.h" +#include "lldb/API/SBFile.h" #include "lldb/API/SBInstruction.h" #include "lldb/API/SBStream.h" @@ -255,10 +256,21 @@ return false; } -void SBInstruction::Print(FILE *out) { - LLDB_RECORD_METHOD(void, SBInstruction, Print, (FILE *), out); +void SBInstruction::Print(FILE *outp) { + LLDB_RECORD_METHOD(void, SBInstruction, Print, (FILE *), outp); + File out(outp, /*take_ownership=*/false); + Print(out); +} + +void SBInstruction::Print(SBFile &out) { + LLDB_RECORD_METHOD(void, SBInstruction, Print, (SBFile &), out); + Print(out.GetFile()); +} + +void SBInstruction::Print(File &out) { + LLDB_RECORD_METHOD(void, SBInstruction, Print, (File &), out); - if (out == nullptr) + if (!out.IsValid()) return; lldb::InstructionSP inst_sp(GetOpaque()); @@ -269,7 +281,7 @@ if (module_sp) module_sp->ResolveSymbolContextForAddress(addr, eSymbolContextEverything, sc); - StreamFile out_stream(out, false); + StreamFile out_stream(out); FormatEntity::Entry format; FormatEntity::Parse("${addr}: ", format); inst_sp->Dump(&out_stream, 0, true, false, nullptr, &sc, nullptr, &format, @@ -358,6 +370,8 @@ LLDB_REGISTER_METHOD(bool, SBInstruction, GetDescription, (lldb::SBStream &)); LLDB_REGISTER_METHOD(void, SBInstruction, Print, (FILE *)); + LLDB_REGISTER_METHOD(void, SBInstruction, Print, (SBFile &)); + LLDB_REGISTER_METHOD(void, SBInstruction, Print, (File &)); LLDB_REGISTER_METHOD(bool, SBInstruction, EmulateWithFrame, (lldb::SBFrame &, uint32_t)); LLDB_REGISTER_METHOD(bool, SBInstruction, DumpEmulation, (const char *)); diff --git a/lldb/source/API/SBInstructionList.cpp b/lldb/source/API/SBInstructionList.cpp --- a/lldb/source/API/SBInstructionList.cpp +++ b/lldb/source/API/SBInstructionList.cpp @@ -11,8 +11,10 @@ #include "lldb/API/SBAddress.h" #include "lldb/API/SBInstruction.h" #include "lldb/API/SBStream.h" +#include "lldb/API/SBFile.h" #include "lldb/Core/Disassembler.h" #include "lldb/Core/Module.h" +#include "lldb/Core/StreamFile.h" #include "lldb/Symbol/SymbolContext.h" #include "lldb/Utility/Stream.h" @@ -118,21 +120,41 @@ void SBInstructionList::Print(FILE *out) { LLDB_RECORD_METHOD(void, SBInstructionList, Print, (FILE *), out); - if (out == nullptr) return; + StreamFile stream(out, false); + GetDescription(stream); } -bool SBInstructionList::GetDescription(lldb::SBStream &description) { +void SBInstructionList::Print(SBFile &out) { + LLDB_RECORD_METHOD(void, SBInstructionList, Print, (SBFile &), out); + if (!out.IsValid()) + return; + StreamFile stream(out.GetFile()); + GetDescription(stream); +} + +void SBInstructionList::Print(File &out) { + LLDB_RECORD_METHOD(void, SBInstructionList, Print, (File &), out); + if (!out.IsValid()) + return; + StreamFile stream(out); + GetDescription(stream); +} + +bool SBInstructionList::GetDescription(lldb::SBStream &stream) { LLDB_RECORD_METHOD(bool, SBInstructionList, GetDescription, - (lldb::SBStream &), description); + (lldb::SBStream &), stream); + return GetDescription(stream.ref()); +} + +bool SBInstructionList::GetDescription(Stream &sref) { if (m_opaque_sp) { size_t num_instructions = GetSize(); if (num_instructions) { // Call the ref() to make sure a stream is created if one deesn't exist // already inside description... - Stream &sref = description.ref(); const uint32_t max_opcode_byte_size = m_opaque_sp->GetInstructionList().GetMaxOpcocdeByteSize(); FormatEntity::Entry format; @@ -200,6 +222,8 @@ LLDB_REGISTER_METHOD(void, SBInstructionList, AppendInstruction, (lldb::SBInstruction)); LLDB_REGISTER_METHOD(void, SBInstructionList, Print, (FILE *)); + LLDB_REGISTER_METHOD(void, SBInstructionList, Print, (SBFile &)); + LLDB_REGISTER_METHOD(void, SBInstructionList, Print, (File &)); LLDB_REGISTER_METHOD(bool, SBInstructionList, GetDescription, (lldb::SBStream &)); LLDB_REGISTER_METHOD(bool, SBInstructionList, diff --git a/lldb/source/API/SBProcess.cpp b/lldb/source/API/SBProcess.cpp --- a/lldb/source/API/SBProcess.cpp +++ b/lldb/source/API/SBProcess.cpp @@ -45,6 +45,8 @@ #include "lldb/API/SBTrace.h" #include "lldb/API/SBTraceOptions.h" #include "lldb/API/SBUnixSignals.h" +#include "lldb/API/SBFile.h" + using namespace lldb; using namespace lldb_private; @@ -331,23 +333,37 @@ return LLDB_RECORD_RESULT(trace_instance); } +void SBProcess::ReportEventState(const SBEvent &event, SBFile &out) const { + LLDB_RECORD_METHOD_CONST(void, SBProcess, ReportEventState, + (const SBEvent &, SBFile &), event, out); + + return ReportEventState(event, out.GetFile()); +} + void SBProcess::ReportEventState(const SBEvent &event, FILE *out) const { LLDB_RECORD_METHOD_CONST(void, SBProcess, ReportEventState, (const lldb::SBEvent &, FILE *), event, out); + File outfile(out, false); + return ReportEventState(event, outfile); +} - if (out == nullptr) +void SBProcess::ReportEventState(const SBEvent &event, File &out) const { + + LLDB_RECORD_METHOD_CONST(void, SBProcess, ReportEventState, + (const SBEvent &, File &), event, out); + + if (!out.IsValid()) return; ProcessSP process_sp(GetSP()); if (process_sp) { const StateType event_state = SBProcess::GetStateFromEvent(event); char message[1024]; - int message_len = ::snprintf( + size_t message_len = ::snprintf( message, sizeof(message), "Process %" PRIu64 " %s\n", process_sp->GetID(), SBDebugger::StateAsCString(event_state)); - if (message_len > 0) - ::fwrite(message, 1, message_len, out); + out.Write((void*)message, message_len); } } @@ -1307,6 +1323,10 @@ (lldb::SBTraceOptions &, lldb::SBError &)); LLDB_REGISTER_METHOD_CONST(void, SBProcess, ReportEventState, (const lldb::SBEvent &, FILE *)); + LLDB_REGISTER_METHOD_CONST(void, SBProcess, ReportEventState, + (const lldb::SBEvent &, File &)); + LLDB_REGISTER_METHOD_CONST(void, SBProcess, ReportEventState, + (const lldb::SBEvent &, SBFile &)); LLDB_REGISTER_METHOD( void, SBProcess, AppendEventStateReport, (const lldb::SBEvent &, lldb::SBCommandReturnObject &)); diff --git a/lldb/source/API/SBStream.cpp b/lldb/source/API/SBStream.cpp --- a/lldb/source/API/SBStream.cpp +++ b/lldb/source/API/SBStream.cpp @@ -14,6 +14,7 @@ #include "lldb/Utility/Status.h" #include "lldb/Utility/Stream.h" #include "lldb/Utility/StreamString.h" +#include "lldb/API/SBFile.h" using namespace lldb; using namespace lldb_private; @@ -107,8 +108,17 @@ void SBStream::RedirectToFileHandle(FILE *fh, bool transfer_fh_ownership) { LLDB_RECORD_METHOD(void, SBStream, RedirectToFileHandle, (FILE *, bool), fh, transfer_fh_ownership); +} + +void SBStream::RedirectToFile(SBFile &file) { + LLDB_RECORD_METHOD(void, SBStream, RedirectToFile, (SBFile &), file) + RedirectToFile(file.GetFile()); +} + +void SBStream::RedirectToFile(File &file) { + LLDB_RECORD_METHOD(void, SBStream, RedirectToFile, (File &), file); - if (fh == nullptr) + if (!file.IsValid()) return; std::string local_data; @@ -118,7 +128,7 @@ if (!m_is_file) local_data = static_cast(m_opaque_up.get())->GetString(); } - m_opaque_up.reset(new StreamFile(fh, transfer_fh_ownership)); + m_opaque_up.reset(new StreamFile(file)); if (m_opaque_up) { m_is_file = true; @@ -189,6 +199,8 @@ LLDB_REGISTER_METHOD(const char *, SBStream, GetData, ()); LLDB_REGISTER_METHOD(size_t, SBStream, GetSize, ()); LLDB_REGISTER_METHOD(void, SBStream, RedirectToFile, (const char *, bool)); + LLDB_REGISTER_METHOD(void, SBStream, RedirectToFile, (File &)); + LLDB_REGISTER_METHOD(void, SBStream, RedirectToFile, (SBFile &)); LLDB_REGISTER_METHOD(void, SBStream, RedirectToFileHandle, (FILE *, bool)); LLDB_REGISTER_METHOD(void, SBStream, RedirectToFileDescriptor, (int, bool)); LLDB_REGISTER_METHOD(void, SBStream, Clear, ()); diff --git a/lldb/source/Commands/CommandObjectGUI.cpp b/lldb/source/Commands/CommandObjectGUI.cpp --- a/lldb/source/Commands/CommandObjectGUI.cpp +++ b/lldb/source/Commands/CommandObjectGUI.cpp @@ -29,8 +29,12 @@ Debugger &debugger = GetDebugger(); lldb::StreamFileSP input_sp = debugger.GetInputFile(); + lldb::StreamFileSP output_sp = debugger.GetOutputFile(); if (input_sp && input_sp->GetFile().GetIsRealTerminal() && - input_sp->GetFile().GetIsInteractive()) { + input_sp->GetFile().GetIsInteractive() && + input_sp->GetFile().GetStream() && + output_sp && output_sp->GetFile().GetStream()) + { IOHandlerSP io_handler_sp(new IOHandlerCursesGUI(debugger)); if (io_handler_sp) debugger.PushIOHandler(io_handler_sp); 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 @@ -824,49 +824,57 @@ repro::DataRecorder *Debugger::GetInputRecorder() { return m_input_recorder; } -void Debugger::SetInputFileHandle(FILE *fh, bool tranfer_ownership, - repro::DataRecorder *recorder) { +Status Debugger::SetInputFile(File &file, repro::DataRecorder *recorder) { + Status error; + m_input_recorder = recorder; - if (m_input_file_sp) - m_input_file_sp->GetFile().SetStream(fh, tranfer_ownership); + + if (!m_input_file_sp) + m_input_file_sp = std::make_shared(file); else - m_input_file_sp = std::make_shared(fh, tranfer_ownership); + m_input_file_sp->GetFile().SetFile(file); File &in_file = m_input_file_sp->GetFile(); - if (!in_file.IsValid()) - in_file.SetStream(stdin, true); + if (!in_file.IsValid()) { + in_file.SetStream(stdin, false); + error.SetErrorString("invalid file"); + } // 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) { - if (m_output_file_sp) - m_output_file_sp->GetFile().SetStream(fh, tranfer_ownership); +Status Debugger::SetOutputFile(File &file) { + Status error; + if (!m_output_file_sp) + m_output_file_sp = std::make_shared(file); else - m_output_file_sp = std::make_shared(fh, tranfer_ownership); + m_output_file_sp->GetFile().SetFile(file); File &out_file = m_output_file_sp->GetFile(); - if (!out_file.IsValid()) + if (!out_file.IsValid()) { out_file.SetStream(stdout, false); - - // Do not create the ScriptInterpreter just for setting the output file - // handle as the constructor will know how to do the right thing on its own. - if (ScriptInterpreter *script_interpreter = - GetScriptInterpreter(/*can_create=*/false)) - script_interpreter->ResetOutputFileHandle(fh); + error.SetErrorString("invalid file"); + } + return error; } -void Debugger::SetErrorFileHandle(FILE *fh, bool tranfer_ownership) { - if (m_error_file_sp) - m_error_file_sp->GetFile().SetStream(fh, tranfer_ownership); +Status Debugger::SetErrorFile(File &file) { + Status error; + if (!m_error_file_sp) + m_error_file_sp = std::make_shared(file); else - m_error_file_sp = std::make_shared(fh, tranfer_ownership); + m_error_file_sp->GetFile().SetFile(file); File &err_file = m_error_file_sp->GetFile(); - if (!err_file.IsValid()) + if (!err_file.IsValid()) { err_file.SetStream(stderr, false); + error.SetErrorString("invalid file"); + } + return error; } void Debugger::SaveInputTerminalState() { 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 @@ -266,7 +266,8 @@ #ifndef LLDB_DISABLE_LIBEDIT bool use_editline = false; - use_editline = m_input_sp->GetFile().GetIsRealTerminal(); + use_editline = GetInputFILE() && GetOutputFILE() && GetErrorFILE() && + m_input_sp->GetFile().GetIsRealTerminal(); if (use_editline) { m_editline_up.reset(new Editline(editline_name, GetInputFILE(), @@ -316,28 +317,70 @@ #endif line.clear(); + if (GetIsInteractive()) { + const char *prompt = nullptr; + + if (m_multi_line && m_curr_line_idx > 0) + prompt = GetContinuationPrompt(); + + if (prompt == nullptr) + prompt = GetPrompt(); + + if (prompt && prompt[0]) { + if (m_output_sp) { + m_output_sp->GetFile().Printf("%s", prompt); + m_output_sp->Flush(); + } + } + } + FILE *in = GetInputFILE(); - if (in) { - if (GetIsInteractive()) { - const char *prompt = nullptr; + char buffer[256]; + bool done = false; + bool got_line = false; - if (m_multi_line && m_curr_line_idx > 0) - prompt = GetContinuationPrompt(); + if (!in && m_input_sp) { + // there is no FILE*, fall back on just reading bytes from the stream. - if (prompt == nullptr) - prompt = GetPrompt(); + size_t pos = m_line_buffer.find('\n'); + if (pos != std::string::npos) { + size_t end = pos; + while (end > 0 && (m_line_buffer[end] == '\n' || m_line_buffer[end] == '\r')) + end--; + line = m_line_buffer.substr(0, end+1); + m_line_buffer = m_line_buffer.substr(pos+1); + done = true; + got_line = true; + } - if (prompt && prompt[0]) { - FILE *out = GetOutputFILE(); - if (out) { - ::fprintf(out, "%s", prompt); - ::fflush(out); + while (!done) { + size_t bytes_read = sizeof(buffer); + m_input_sp->GetFile().Read((void*)buffer, bytes_read); + if (bytes_read) { + auto bytes = llvm::StringRef(buffer, bytes_read); + size_t pos = bytes.find('\n'); + if (pos != llvm::StringRef::npos) { + size_t end = pos; + while (end > 0 && (bytes[end] == '\n' || bytes[end] == '\r')) + end--; + line = m_line_buffer; + line.append(bytes.substr(0, end+1)); + m_line_buffer = bytes.substr(pos+1); + done = true; + got_line = true; + } else { + m_line_buffer.append(bytes); } + } else { + // No more input file, we are done... + SetIsDone(true); + done = true; } } - char buffer[256]; - bool done = false; - bool got_line = false; + return got_line; + } + + if (in) { m_editing = true; while (!done) { #ifdef _WIN32 @@ -490,10 +533,11 @@ // Show line numbers if we are asked to std::string line; if (m_base_line_number > 0 && GetIsInteractive()) { - FILE *out = GetOutputFILE(); - if (out) - ::fprintf(out, "%u%s", m_base_line_number + (uint32_t)lines.GetSize(), - GetPrompt() == nullptr ? " " : ""); + if (m_output_sp) { + m_output_sp->GetFile().Printf("%u%s", + m_base_line_number + (uint32_t)lines.GetSize(), + GetPrompt() == nullptr ? " " : ""); + } } m_curr_line_idx = lines.GetSize(); diff --git a/lldb/source/Core/StreamFile.cpp b/lldb/source/Core/StreamFile.cpp --- a/lldb/source/Core/StreamFile.cpp +++ b/lldb/source/Core/StreamFile.cpp @@ -26,6 +26,9 @@ StreamFile::StreamFile(FILE *fh, bool transfer_ownership) : Stream(), m_file(fh, transfer_ownership) {} +StreamFile::StreamFile(File &file) + : Stream(), m_file(file) {} + StreamFile::StreamFile(const char *path) : Stream(), m_file() { FileSystem::Instance().Open(m_file, FileSpec(path), File::eOpenOptionWrite | diff --git a/lldb/source/Expression/REPL.cpp b/lldb/source/Expression/REPL.cpp --- a/lldb/source/Expression/REPL.cpp +++ b/lldb/source/Expression/REPL.cpp @@ -418,7 +418,7 @@ .SetBaseLineNumber(m_code.GetSize() + 1); } if (extra_line) { - fprintf(output_sp->GetFile().GetStream(), "\n"); + output_sp->GetFile().Printf("\n"); } } } diff --git a/lldb/source/Host/common/File.cpp b/lldb/source/Host/common/File.cpp --- a/lldb/source/Host/common/File.cpp +++ b/lldb/source/Host/common/File.cpp @@ -91,21 +91,46 @@ return kInvalidDescriptor; } -IOObject::WaitableHandle File::GetWaitableHandle() { return m_descriptor; } +IOObject::WaitableHandle File::GetWaitableHandle() { return GetDescriptor(); } void File::SetDescriptor(int fd, bool transfer_ownership) { if (IsValid()) Close(); m_descriptor = fd; - m_should_close_fd = transfer_ownership; + if (transfer_ownership) { + m_fops = std::make_shared(fd, true); + } +} + +const char *File::GetFdopenMode() const { + if (m_options) + return GetStreamOpenModeFromOptions(m_options); + int fdflags = ::fcntl(m_descriptor, F_GETFL); + if (fdflags >= 0) { + if (fdflags & O_RDWR) { + return "r+"; + } else if (fdflags & O_WRONLY) { + return "w"; + } else { + return "r"; + } + } + return nullptr; } FILE *File::GetStream() { if (!StreamIsValid()) { - if (DescriptorIsValid()) { - const char *mode = GetStreamOpenModeFromOptions(m_options); + if (DescriptorIsValid() && !OverridesIO()) { + const char *mode = GetFdopenMode(); if (mode) { - if (!m_should_close_fd) { + if (!m_fops) { + m_fops = std::make_shared(); + } + if (m_fops->m_stream) { + m_stream = m_fops->m_stream; + return m_stream; + } + if (!m_fops->m_own_descriptor) { // We must duplicate the file descriptor if we don't own it because when you // call fdopen, the stream will own the fd #ifdef _WIN32 @@ -113,7 +138,8 @@ #else m_descriptor = dup(GetDescriptor()); #endif - m_should_close_fd = true; + m_fops->m_descriptor = m_descriptor; + m_fops->m_own_descriptor = true; } m_stream = @@ -123,8 +149,9 @@ // the descriptor because fclose() will close it for us if (m_stream) { - m_own_stream = true; - m_should_close_fd = false; + m_fops->m_own_stream = true; + m_fops->m_stream = m_stream; + m_fops->m_own_descriptor = false; } } } @@ -136,7 +163,26 @@ if (IsValid()) Close(); m_stream = fh; - m_own_stream = transfer_ownership; + if (transfer_ownership) { + m_fops = std::make_shared(fh, transfer_ownership); + } +} + +void File::SetFile(const File &file) { + if (IsValid()) + Close(); + m_descriptor = file.m_descriptor; + m_stream = file.m_stream; + m_options = file.m_options; + m_is_interactive = file.m_is_interactive; + m_is_real_terminal = file.m_is_real_terminal; + m_supports_colors = file.m_supports_colors; + m_fops = file.m_fops; +} + +File &File::operator=(const File &file) { + SetFile(file); + return *this; } uint32_t File::GetPermissions(Status &error) const { @@ -155,34 +201,58 @@ return 0; } -Status File::Close() { +Status FileOps::Close() { Status error; - if (StreamIsValid() && m_own_stream) { + if (m_stream != File::kInvalidStream && m_own_stream) { if (::fclose(m_stream) == EOF) error.SetErrorToErrno(); } - - if (DescriptorIsValid() && m_should_close_fd) { + if (m_descriptor != File::kInvalidDescriptor && m_own_descriptor) { if (::close(m_descriptor) != 0) error.SetErrorToErrno(); } + m_descriptor = File::kInvalidDescriptor; + m_stream = File::kInvalidStream; + return error; +} + +FileOps::~FileOps() { + Close(); +} + +Status File::Close() { + Status error; + if (m_stream) { + int r = llvm::sys::RetryAfterSignal(EOF, ::fflush, m_stream); + if (r != 0) + error.SetErrorToErrno(); + } + if (m_fops) { + if (m_fops.unique()) { + error = m_fops->Close(); + } + m_fops.reset(); + } m_descriptor = kInvalidDescriptor; m_stream = kInvalidStream; m_options = 0; - m_own_stream = false; - m_should_close_fd = false; m_is_interactive = eLazyBoolCalculate; m_is_real_terminal = eLazyBoolCalculate; + m_supports_colors = eLazyBoolCalculate; return error; } -void File::Clear() { - m_stream = nullptr; - m_descriptor = kInvalidDescriptor; - m_options = 0; - m_own_stream = false; - m_is_interactive = m_supports_colors = m_is_real_terminal = - eLazyBoolCalculate; +FILE *File::TakeStreamAndClear() { + FILE *stream = NULL; + GetStream(); + if (!m_fops) { + stream = m_stream; + } else if (m_fops && m_fops.unique()) { + stream = m_fops->m_stream; + m_fops->m_own_stream = false; + } + Close(); + return stream; } Status File::GetFileSpec(FileSpec &file_spec) const { @@ -221,8 +291,11 @@ } off_t File::SeekFromStart(off_t offset, Status *error_ptr) { - off_t result = 0; - if (DescriptorIsValid()) { + off_t result = -1; + if (OverridesIO()) { + if (error_ptr) + error_ptr->SetErrorString("unsupported operation"); + } else if (DescriptorIsValid()) { result = ::lseek(m_descriptor, offset, SEEK_SET); if (error_ptr) { @@ -248,7 +321,10 @@ off_t File::SeekFromCurrent(off_t offset, Status *error_ptr) { off_t result = -1; - if (DescriptorIsValid()) { + if (OverridesIO()) { + if (error_ptr) + error_ptr->SetErrorString("unsupported operation"); + } else if (DescriptorIsValid()) { result = ::lseek(m_descriptor, offset, SEEK_CUR); if (error_ptr) { @@ -274,7 +350,10 @@ off_t File::SeekFromEnd(off_t offset, Status *error_ptr) { off_t result = -1; - if (DescriptorIsValid()) { + if (OverridesIO()) { + if (error_ptr) + error_ptr->SetErrorString("unsupported operation"); + } else if (DescriptorIsValid()) { result = ::lseek(m_descriptor, offset, SEEK_END); if (error_ptr) { @@ -298,9 +377,15 @@ return result; } +Status FileOps::Flush() { + LLVM_BUILTIN_TRAP; +} + Status File::Flush() { Status error; - if (StreamIsValid()) { + if (OverridesIO()) { + error = m_fops->Flush(); + } else if (StreamIsValid()) { if (llvm::sys::RetryAfterSignal(EOF, ::fflush, m_stream) == EOF) error.SetErrorToErrno(); } else if (!DescriptorIsValid()) { @@ -332,9 +417,17 @@ #define MAX_WRITE_SIZE INT_MAX #endif +Status FileOps::Read(void *buf, size_t &num_bytes) { + LLVM_BUILTIN_TRAP; +} + Status File::Read(void *buf, size_t &num_bytes) { Status error; + if (OverridesIO()) { + return m_fops->Read(buf, num_bytes); + } + #if defined(MAX_READ_SIZE) if (num_bytes > MAX_READ_SIZE) { uint8_t *p = (uint8_t *)buf; @@ -391,50 +484,43 @@ return error; } +Status FileOps::Write(const void *buf, size_t &num_bytes) { + LLVM_BUILTIN_TRAP; +} + Status File::Write(const void *buf, size_t &num_bytes) { Status error; -#if defined(MAX_WRITE_SIZE) - if (num_bytes > MAX_WRITE_SIZE) { + if (OverridesIO()) { + return m_fops->Write(buf, num_bytes); + } + + if (DescriptorIsValid()) { const uint8_t *p = (const uint8_t *)buf; size_t bytes_left = num_bytes; - // Init the num_bytes written to zero - num_bytes = 0; - + size_t bytes_written = 0; while (bytes_left > 0) { - size_t curr_num_bytes; - if (bytes_left > MAX_WRITE_SIZE) - curr_num_bytes = MAX_WRITE_SIZE; - else - curr_num_bytes = bytes_left; - - error = Write(p + num_bytes, curr_num_bytes); - - // Update how many bytes were read - num_bytes += curr_num_bytes; - if (bytes_left < curr_num_bytes) - bytes_left = 0; - else - bytes_left -= curr_num_bytes; - - if (error.Fail()) + size_t curr_num_bytes = bytes_left; + #if defined(MAX_WRITE_SIZE) + if (bytes_left > MAX_WRITE_SIZE) + curr_num_bytes = MAX_WRITE_SIZE; + #endif + + int r = llvm::sys::RetryAfterSignal(-1, ::write, m_descriptor, p+bytes_written, curr_num_bytes); + if (r < 0) { + error.SetErrorToErrno(); + break; + } else if (r > 0) { + bytes_written += r; + bytes_left -= r; + } else { break; + } } - return error; - } -#endif + num_bytes = bytes_written; - ssize_t bytes_written = -1; - if (DescriptorIsValid()) { - bytes_written = - llvm::sys::RetryAfterSignal(-1, ::write, m_descriptor, buf, num_bytes); - if (bytes_written == -1) { - error.SetErrorToErrno(); - num_bytes = 0; - } else - num_bytes = bytes_written; } else if (StreamIsValid()) { - bytes_written = ::fwrite(buf, 1, num_bytes, m_stream); + size_t bytes_written = ::fwrite(buf, 1, num_bytes, m_stream); if (bytes_written == 0) { if (::feof(m_stream)) @@ -456,6 +542,11 @@ Status File::Read(void *buf, size_t &num_bytes, off_t &offset) { Status error; + if (OverridesIO()) { + error.SetErrorString("unsupported operation"); + return error; + } + #if defined(MAX_READ_SIZE) if (num_bytes > MAX_READ_SIZE) { uint8_t *p = (uint8_t *)buf; @@ -516,6 +607,11 @@ Status File::Write(const void *buf, size_t &num_bytes, off_t &offset) { Status error; + if (OverridesIO()) { + error.SetErrorString("unsupported operation"); + return error; + } + #if defined(MAX_WRITE_SIZE) if (num_bytes > MAX_WRITE_SIZE) { const uint8_t *p = (const uint8_t *)buf; @@ -589,7 +685,7 @@ // Print some formatted output to the stream. size_t File::PrintfVarArg(const char *format, va_list args) { size_t result = 0; - if (DescriptorIsValid()) { + if (DescriptorIsValid() || OverridesIO()) { char *s = nullptr; result = vasprintf(&s, format, args); if (s != nullptr) { diff --git a/lldb/source/Host/common/Socket.cpp b/lldb/source/Host/common/Socket.cpp --- a/lldb/source/Host/common/Socket.cpp +++ b/lldb/source/Host/common/Socket.cpp @@ -74,9 +74,11 @@ Socket::Socket(SocketProtocol protocol, bool should_close, bool child_processes_inherit) - : IOObject(eFDTypeSocket, should_close), m_protocol(protocol), + : IOObject(eFDTypeSocket), + m_protocol(protocol), m_socket(kInvalidSocketValue), - m_child_processes_inherit(child_processes_inherit) {} + m_child_processes_inherit(child_processes_inherit), + m_should_close_fd(should_close) {} Socket::~Socket() { Close(); } diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h --- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h @@ -84,14 +84,19 @@ PythonObject(const PythonObject &rhs) : m_py_obj(nullptr) { Reset(rhs); } + PythonObject (PythonObject &&rhs) { + m_py_obj = rhs.m_py_obj; + rhs.m_py_obj = nullptr; + } + virtual ~PythonObject() { Reset(); } void Reset() { // Avoid calling the virtual method since it's not necessary // to actually validate the type of the PyObject if we're // just setting to null. - if (Py_IsInitialized()) - Py_XDECREF(m_py_obj); + if (m_py_obj && Py_IsInitialized()) + Py_DECREF(m_py_obj); m_py_obj = nullptr; } @@ -454,7 +459,7 @@ class PythonFile : public PythonObject { public: PythonFile(); - PythonFile(File &file, const char *mode); + PythonFile(File &file, const char *mode = nullptr); PythonFile(const char *path, const char *mode); PythonFile(PyRefType type, PyObject *o); @@ -465,11 +470,12 @@ using PythonObject::Reset; void Reset(PyRefType type, PyObject *py_obj) override; - void Reset(File &file, const char *mode); + void Reset(File &file, const char *mode = nullptr); static uint32_t GetOptionsFromMode(llvm::StringRef mode); - bool GetUnderlyingFile(File &file) const; + Status ConvertToFile(File &file, bool borrowed = false); + Status ConvertToFileForcingUseOfScriptingIOMethods(File &file, bool borrowed = false); }; } // namespace lldb_private diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp --- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp @@ -19,16 +19,28 @@ #include "lldb/Host/FileSystem.h" #include "lldb/Interpreter/ScriptInterpreter.h" #include "lldb/Utility/Stream.h" +#include "lldb/Utility/Log.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/ConvertUTF.h" #include "llvm/Support/Errno.h" +#include "llvm/Support/Casting.h" #include using namespace lldb_private; using namespace lldb; +template +static T Owned(PyObject *obj) { + return T(PyRefType::Owned, obj); +} + +template +static T Borrowed(PyObject *obj) { + return T(PyRefType::Borrowed, obj); +} + void StructuredPythonObject::Dump(Stream &s, bool pretty_print) const { s << "Python Obj: 0x" << GetValue(); } @@ -960,6 +972,8 @@ PythonFile::~PythonFile() {} bool PythonFile::Check(PyObject *py_obj) { + if (!py_obj) + return false; #if PY_MAJOR_VERSION < 3 return PyFile_Check(py_obj); #else @@ -967,9 +981,7 @@ // first-class object type anymore. `PyFile_FromFd` is just a thin wrapper // over `io.open()`, which returns some object derived from `io.IOBase`. As a // result, the only way to detect a file in Python 3 is to check whether it - // inherits from `io.IOBase`. Since it is possible for non-files to also - // inherit from `io.IOBase`, we additionally verify that it has the `fileno` - // attribute, which should guarantee that it is backed by the file system. + // inherits from `io.IOBase`. PythonObject io_module(PyRefType::Owned, PyImport_ImportModule("io")); PythonDictionary io_dict(PyRefType::Borrowed, PyModule_GetDict(io_module.get())); @@ -979,8 +991,6 @@ if (1 != PyObject_IsSubclass(object_type.get(), io_base_class.get())) return false; - if (!object_type.HasAttribute("fileno")) - return false; return true; #endif @@ -1001,24 +1011,6 @@ PythonObject::Reset(PyRefType::Borrowed, result.get()); } -void PythonFile::Reset(File &file, const char *mode) { - if (!file.IsValid()) { - Reset(); - return; - } - - char *cmode = const_cast(mode); -#if PY_MAJOR_VERSION >= 3 - Reset(PyRefType::Owned, PyFile_FromFd(file.GetDescriptor(), nullptr, cmode, - -1, nullptr, "ignore", nullptr, 0)); -#else - // Read through the Python source, doesn't seem to modify these strings - Reset(PyRefType::Owned, - PyFile_FromFile(file.GetStream(), const_cast(""), cmode, - nullptr)); -#endif -} - uint32_t PythonFile::GetOptionsFromMode(llvm::StringRef mode) { if (mode.empty()) return 0; @@ -1036,17 +1028,313 @@ .Default(0); } -bool PythonFile::GetUnderlyingFile(File &file) const { +class GIL { +public: + GIL() { + m_state = PyGILState_Ensure(); + } + ~GIL() { + PyGILState_Release(m_state); + } +protected: + PyGILState_STATE m_state; +}; + +static Status PyException(const char *caller) { + Status error; + if (PyErr_Occurred()) { + PyObject *exception_type, *exception, *tb; + PyErr_Fetch(&exception_type, &exception, &tb); + PyErr_NormalizeException(&exception_type, &exception, &tb); + if (exception) { + PyObject *repr = PyObject_Repr(exception); + if (repr) { + PyObject *repr_bytes = PyUnicode_AsEncodedString(repr, "utf-8", nullptr); + Py_XDECREF(repr); + if (repr_bytes) { + error.SetErrorString(PyBytes_AS_STRING(repr_bytes)); + Py_XDECREF(repr_bytes); + } + } + } + if (!error.Fail()) { + error.SetErrorString("unknown python exception"); + } + Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_SCRIPT); + if (log) { + log->Printf("%s failed with exception: %s", caller, error.AsCString()); + } + } + return error; +} + +class SimplePythonFileOps : public FileOps { + friend class PythonFile; +public: + + SimplePythonFileOps(int fd, PyObject *py_obj, bool borrowed) : + FileOps(fd, false), + m_py_obj(py_obj), + m_borrowed(borrowed) + { + m_kind = FOK_SimplePythonFileOps; + Py_INCREF(m_py_obj); + } + + Status Close() override { + FileOps::Close(); + GIL takeGIL; + if (!m_borrowed) + PyObject_CallMethod(m_py_obj, "close", "()"); + Py_XDECREF(m_py_obj); + return PyException("Close"); + }; + + static bool classof(const FileOps *fops) { + return fops->GetKind() >= FOK_SimplePythonFileOps && + fops->GetKind() <= FOK_Last_SimplePythonFileOps; + } + +protected: + PyObject *m_py_obj; + bool m_borrowed; +}; + +void PythonFile::Reset(File &file, const char *mode) { + if (!file.IsValid()) { + Reset(); + return; + } + + if (file.m_fops) { + auto *fops = llvm::dyn_cast(&*file.m_fops); + if (fops) { + Reset(PyRefType::Borrowed, fops->m_py_obj); + return; + } + } + + if (!mode) + mode = file.GetFdopenMode(); + if (!mode) { + Reset(); + return; + } + +#if PY_MAJOR_VERSION >= 3 + Reset(PyRefType::Owned, PyFile_FromFd(file.GetDescriptor(), nullptr, mode, + -1, nullptr, "ignore", nullptr, 0)); +#else + // Read through the Python source, doesn't seem to modify these strings + Reset(PyRefType::Owned, + PyFile_FromFile(file.GetStream(), + const_cast(""), const_cast(mode), + nullptr)); +#endif +} + +Status PythonFile::ConvertToFile(File &file, bool borrowed) { + Status error; + if (!IsValid()) { + error.SetErrorString("invalid PythonFile"); + return error; + } + int fd = PyObject_AsFileDescriptor(m_py_obj); + if (fd < 0) { + return ConvertToFileForcingUseOfScriptingIOMethods(file, borrowed); + } + if (borrowed) { + // In this case we we don't need to retain the python + // object at all. + file.SetDescriptor(fd, false); + } else { + auto fops = std::make_shared(fd, m_py_obj, borrowed); + file = File(fops, fd); + } + if (!file.IsValid()) { + error.SetErrorString("invalid file"); + } + return error; +} + +#if PY_MAJOR_VERSION >= 3 + +class PythonBuffer { +public: + PythonBuffer(PythonObject &obj, int flags = PyBUF_SIMPLE) : m_buffer({}) { + PyObject_GetBuffer(obj.get(), &m_buffer, flags); + } + ~PythonBuffer() { + if (m_buffer.obj) { + PyBuffer_Release(&m_buffer); + } + } + Py_buffer &get() { return m_buffer; } +protected: + Py_buffer m_buffer; +}; + +class BinaryPythonFileOps : public SimplePythonFileOps { + friend class PythonFile; +public: + BinaryPythonFileOps(int fd, PyObject *py_obj, bool borrowed) + : SimplePythonFileOps(fd, py_obj, borrowed) + { + m_kind = FOK_BinaryPythonFileOps; + m_overrides_io = true; + } + + Status Write(const void *buf, size_t &num_bytes) override { + GIL takeGIL; + auto pybuffer = Owned( + PyMemoryView_FromMemory(const_cast((const char*)buf), num_bytes, PyBUF_READ)); + auto bytes_written = Owned( + PyObject_CallMethod(m_py_obj, "write", "(O)", pybuffer.get())); + if (PyErr_Occurred()) + return PyException("Write"); + long l_bytes_written = PyLong_AsLong(bytes_written.get()); + if (PyErr_Occurred()) + return PyException("Write"); + num_bytes = l_bytes_written; + if (l_bytes_written < 0 || (unsigned long)l_bytes_written != num_bytes) { + return Status("overflow"); + } + return Status(); + } + + Status Read(void *buf, size_t &num_bytes) override { + GIL takeGIL; + auto pybuffer_obj = Owned( + PyObject_CallMethod(m_py_obj, "read", "(L)", (unsigned long long)num_bytes)); + if (PyErr_Occurred()) + return PyException("Read"); + if (pybuffer_obj.IsNone()) { + // EOF + num_bytes = 0; + return Status(); + } + PythonBuffer pybuffer(pybuffer_obj); + if (PyErr_Occurred()) + return PyException("Read"); + memcpy(buf, pybuffer.get().buf, pybuffer.get().len); + num_bytes = pybuffer.get().len; + return Status(); + } + + Status Flush() override { + GIL takeGIL; + PyErr_Clear(); + PyObject_CallMethod(m_py_obj, "flush", "()"); + return PyException("Flush"); + } +}; + +class TextPythonFileOps : public SimplePythonFileOps { + friend class PythonFile; +public: + TextPythonFileOps(int fd, PyObject *py_obj, bool borrowed) + : SimplePythonFileOps(fd, py_obj, borrowed) + { + m_kind = FOK_TextPythonFileOps; + m_overrides_io = true; + } + + Status Write(const void *buf, size_t &num_bytes) override { + GIL takeGIL; + auto pystring = Owned( + PyUnicode_FromStringAndSize((const char *)buf, num_bytes)); + if (PyErr_Occurred()) + return PyException("Write"); + auto bytes_written = Owned( + PyObject_CallMethod(m_py_obj, "write", "(O)", pystring.get())); + if (PyErr_Occurred()) + return PyException("Write"); + long l_bytes_written = PyLong_AsLong(bytes_written.get()); + if (PyErr_Occurred()) + return PyException("Write"); + num_bytes = l_bytes_written; + if (l_bytes_written < 0 || (unsigned long)l_bytes_written != num_bytes) { + return Status("overflow"); + } + return Status(); + } + + Status Read(void *buf, size_t &num_bytes) override { + if (num_bytes < 6) { + return Status("can't read less than 6 bytes from a utf8 text stream"); + } + GIL takeGIL; + size_t num_chars = num_bytes / 6; + auto pystring = Owned( + PyObject_CallMethod(m_py_obj, "read", "(L)", (unsigned long long)num_chars)); + if (pystring.IsNone()) { + // EOF + num_bytes = 0; + return Status(); + } + if (PyErr_Occurred()) + return PyException("Read"); + if (!PyUnicode_Check(pystring.get())) + return Status("read() didn't return a str"); + Py_ssize_t size; + const char *utf8 = PyUnicode_AsUTF8AndSize(pystring.get(), &size); + if (!utf8 || PyErr_Occurred()) + return PyException("Read"); + assert(size >= 0 && (size_t)size <= num_bytes); + memcpy(buf, utf8, size); + num_bytes = size; + return Status(); + } + + Status Flush() override { + GIL takeGIL; + PyErr_Clear(); + PyObject_CallMethod(m_py_obj, "flush", "()"); + return PyException("Flush"); + } +}; + +#endif + +Status PythonFile::ConvertToFileForcingUseOfScriptingIOMethods(File &file, bool borrowed) { +#if PY_MAJOR_VERSION >= 3 + if (!IsValid()) - return false; + return Status("invalid PythonFile"); + int fd = PyObject_AsFileDescriptor(m_py_obj); + if (fd < 0) { + PyErr_Clear(); + fd = -1; + } + + auto io_module = Owned(PyImport_ImportModule("io")); + auto io_dict = Borrowed(PyModule_GetDict(io_module.get())); + auto textIOBase = io_dict.GetItemForKey(PythonString("TextIOBase")); + auto rawIOBase = io_dict.GetItemForKey(PythonString("BufferedIOBase")); + auto bufferedIOBase = io_dict.GetItemForKey(PythonString("RawIOBase")); + + if (PyObject_IsInstance(m_py_obj, textIOBase.get())) { + auto fops = std::make_shared(fd, m_py_obj, borrowed); + file = File(fops, fd); + } else if (PyObject_IsInstance(m_py_obj, rawIOBase.get()) || + PyObject_IsInstance(m_py_obj, bufferedIOBase.get())) { + auto fops = std::make_shared(fd, m_py_obj, borrowed); + file = File(fops, fd); + } else { + file.Close(); + return Status("python file is neither text nor binary"); + } + + if (!file.IsValid()) + return Status("invalid file"); + + return Status(); + +#else file.Close(); - // We don't own the file descriptor returned by this function, make sure the - // File object knows about that. - file.SetDescriptor(PyObject_AsFileDescriptor(m_py_obj), false); - PythonString py_mode = GetAttributeValue("mode").AsType(); - file.SetOptions(PythonFile::GetOptionsFromMode(py_mode.GetString())); - return file.IsValid(); + return Status("not implemented on python2"); +#endif } #endif diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -382,7 +382,7 @@ ScriptInterpreterPythonImpl::Locker::Locker( ScriptInterpreterPythonImpl *py_interpreter, uint16_t on_entry, - uint16_t on_leave, FILE *in, FILE *out, FILE *err) + uint16_t on_leave, File *in, File *out, File *err) : ScriptInterpreterLocker(), m_teardown_session((on_leave & TearDownSession) == TearDownSession), m_python_interpreter(py_interpreter) { @@ -411,9 +411,10 @@ return true; } -bool ScriptInterpreterPythonImpl::Locker::DoInitSession(uint16_t on_entry_flags, - FILE *in, FILE *out, - FILE *err) { +bool ScriptInterpreterPythonImpl::Locker::DoInitSession( + uint16_t on_entry_flags, + File *in, File *out, File *err) +{ if (!m_python_interpreter) return false; return m_python_interpreter->EnterSession(on_entry_flags, in, out, err); @@ -609,8 +610,6 @@ return std::make_shared(debugger); } -void ScriptInterpreterPythonImpl::ResetOutputFileHandle(FILE *fh) {} - void ScriptInterpreterPythonImpl::LeaveSession() { Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_SCRIPT)); if (log) @@ -647,7 +646,7 @@ } bool ScriptInterpreterPythonImpl::SetStdHandle(File &file, const char *py_name, - PythonFile &save_file, + PythonObject &save_file, const char *mode) { if (file.IsValid()) { // Flush the file before giving it to python to avoid interleaved output. @@ -655,8 +654,7 @@ PythonDictionary &sys_module_dict = GetSysModuleDictionary(); - save_file = sys_module_dict.GetItemForKey(PythonString(py_name)) - .AsType(); + save_file = sys_module_dict.GetItemForKey(PythonString(py_name)); PythonFile new_file(file, mode); sys_module_dict.SetItemForKey(PythonString(py_name), new_file); @@ -667,7 +665,7 @@ } bool ScriptInterpreterPythonImpl::EnterSession(uint16_t on_entry_flags, - FILE *in, FILE *out, FILE *err) { + File *inp, File *outp, File *errp) { // If we have already entered the session, without having officially 'left' // it, then there is no need to 'enter' it again. Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_SCRIPT)); @@ -716,31 +714,35 @@ PythonDictionary &sys_module_dict = GetSysModuleDictionary(); if (sys_module_dict.IsValid()) { - File in_file(in, false); - File out_file(out, false); - File err_file(err, false); + File in, out, err; + if (inp) + in = *inp; + if (outp) + out = *outp; + if (errp) + err = *errp; lldb::StreamFileSP in_sp; lldb::StreamFileSP out_sp; lldb::StreamFileSP err_sp; - if (!in_file.IsValid() || !out_file.IsValid() || !err_file.IsValid()) + if (!in.IsValid() || !out.IsValid() || !err.IsValid()) m_debugger.AdoptTopIOHandlerFilesIfInvalid(in_sp, out_sp, err_sp); if (on_entry_flags & Locker::NoSTDIN) { m_saved_stdin.Reset(); } else { - if (!SetStdHandle(in_file, "stdin", m_saved_stdin, "r")) { + if (!SetStdHandle(in, "stdin", m_saved_stdin, "r")) { if (in_sp) SetStdHandle(in_sp->GetFile(), "stdin", m_saved_stdin, "r"); } } - if (!SetStdHandle(out_file, "stdout", m_saved_stdout, "w")) { + if (!SetStdHandle(out, "stdout", m_saved_stdout, "w")) { if (out_sp) SetStdHandle(out_sp->GetFile(), "stdout", m_saved_stdout, "w"); } - if (!SetStdHandle(err_file, "stderr", m_saved_stderr, "w")) { + if (!SetStdHandle(err, "stderr", m_saved_stderr, "w")) { if (err_sp) SetStdHandle(err_sp->GetFile(), "stderr", m_saved_stderr, "w"); } @@ -889,9 +891,9 @@ ::setbuf(outfile_handle, nullptr); result->SetImmediateOutputFile( - debugger.GetOutputFile()->GetFile().GetStream()); + debugger.GetOutputFile()->GetFile()); result->SetImmediateErrorFile( - debugger.GetErrorFile()->GetFile().GetStream()); + debugger.GetErrorFile()->GetFile()); } } } @@ -912,9 +914,9 @@ error_file_sp = output_file_sp; } - FILE *in_file = input_file_sp->GetFile().GetStream(); - FILE *out_file = output_file_sp->GetFile().GetStream(); - FILE *err_file = error_file_sp->GetFile().GetStream(); + File &in_file = input_file_sp->GetFile(); + File &out_file = output_file_sp->GetFile(); + File &err_file = error_file_sp->GetFile(); bool success = false; { // WARNING! It's imperative that this RAII scope be as tight as @@ -930,8 +932,8 @@ Locker::AcquireLock | Locker::InitSession | (options.GetSetLLDBGlobals() ? Locker::InitGlobals : 0) | ((result && result->GetInteractive()) ? 0 : Locker::NoSTDIN), - Locker::FreeAcquiredLock | Locker::TearDownSession, in_file, out_file, - err_file); + Locker::FreeAcquiredLock | Locker::TearDownSession, + &in_file, &out_file, &err_file); // Find the correct script interpreter dictionary in the main module. PythonDictionary &session_dict = GetSessionDictionary(); @@ -958,9 +960,8 @@ } // Flush our output and error file handles - ::fflush(out_file); - if (out_file != err_file) - ::fflush(err_file); + out_file.Flush(); + err_file.Flush(); } if (join_read_thread) { diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h @@ -256,8 +256,6 @@ void SetWatchpointCommandCallback(WatchpointOptions *wp_options, const char *oneliner) override; - void ResetOutputFileHandle(FILE *new_fh) override; - const char *GetDictionaryName() { return m_dictionary_name.c_str(); } PyThreadState *GetThreadState() { return m_command_thread_state; } @@ -296,17 +294,19 @@ TearDownSession = 0x0004 }; - Locker(ScriptInterpreterPythonImpl *py_interpreter = nullptr, + Locker(ScriptInterpreterPythonImpl *py_interpreter, uint16_t on_entry = AcquireLock | InitSession, - uint16_t on_leave = FreeLock | TearDownSession, FILE *in = nullptr, - FILE *out = nullptr, FILE *err = nullptr); + uint16_t on_leave = FreeLock | TearDownSession, + File *in = nullptr, + File *out = nullptr, + File *err = nullptr); ~Locker() override; private: bool DoAcquireLock(); - bool DoInitSession(uint16_t on_entry_flags, FILE *in, FILE *out, FILE *err); + bool DoInitSession(uint16_t on_entry_flags, File *in, File *out, File *err); bool DoFreeLock(); @@ -314,7 +314,6 @@ bool m_teardown_session; ScriptInterpreterPythonImpl *m_python_interpreter; - // FILE* m_tmp_fh; PyGILState_STATE m_GILState; }; @@ -343,7 +342,7 @@ static void AddToSysPath(AddLocation location, std::string path); - bool EnterSession(uint16_t on_entry_flags, FILE *in, FILE *out, FILE *err); + bool EnterSession(uint16_t on_entry_flags, File *in, File *out, File *err); void LeaveSession(); @@ -371,12 +370,12 @@ bool GetEmbeddedInterpreterModuleObjects(); - bool SetStdHandle(File &file, const char *py_name, PythonFile &save_file, + bool SetStdHandle(File &file, const char *py_name, PythonObject &save_file, const char *mode); - PythonFile m_saved_stdin; - PythonFile m_saved_stdout; - PythonFile m_saved_stderr; + PythonObject m_saved_stdin; + PythonObject m_saved_stdout; + PythonObject m_saved_stderr; PythonObject m_main_module; PythonDictionary m_session_dict; PythonDictionary m_sys_module_dict; diff --git a/lldb/third_party/Python/module/unittest2/unittest2/loader.py b/lldb/third_party/Python/module/unittest2/unittest2/loader.py --- a/lldb/third_party/Python/module/unittest2/unittest2/loader.py +++ b/lldb/third_party/Python/module/unittest2/unittest2/loader.py @@ -117,7 +117,7 @@ return self.loadTestsFromModule(obj) elif isinstance(obj, type) and issubclass(obj, unittest.TestCase): return self.loadTestsFromTestCase(obj) - elif (isinstance(obj, types.UnboundMethodType) and + elif (isinstance(obj, (types.MethodType, types.FunctionType)) and isinstance(parent, type) and issubclass(parent, case.TestCase)): return self.suiteClass([parent(obj.__name__)]) diff --git a/lldb/tools/driver/Driver.cpp b/lldb/tools/driver/Driver.cpp --- a/lldb/tools/driver/Driver.cpp +++ b/lldb/tools/driver/Driver.cpp @@ -16,6 +16,7 @@ #include "lldb/API/SBReproducer.h" #include "lldb/API/SBStream.h" #include "lldb/API/SBStringList.h" +#include "lldb/API/SBFile.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/ConvertUTF.h" @@ -499,16 +500,16 @@ SBCommandReturnObject result; sb_interpreter.SourceInitFileInHomeDirectory(result); if (m_option_data.m_debug_mode) { - result.PutError(m_debugger.GetErrorFileHandle()); - result.PutOutput(m_debugger.GetOutputFileHandle()); + result.PutError(m_debugger.GetErrorFile().GetFile()); + result.PutOutput(m_debugger.GetOutputFile().GetFile()); } // Source the local .lldbinit file if it exists and we're allowed to source. // Here we want to always print the return object because it contains the // warning and instructions to load local lldbinit files. sb_interpreter.SourceInitFileInCurrentWorkingDirectory(result); - result.PutError(m_debugger.GetErrorFileHandle()); - result.PutOutput(m_debugger.GetOutputFileHandle()); + result.PutError(m_debugger.GetErrorFile().GetFile()); + result.PutOutput(m_debugger.GetOutputFile().GetFile()); // We allow the user to specify an exit code when calling quit which we will // return when exiting. @@ -574,8 +575,8 @@ } if (m_option_data.m_debug_mode) { - result.PutError(m_debugger.GetErrorFileHandle()); - result.PutOutput(m_debugger.GetOutputFileHandle()); + result.PutError(m_debugger.GetErrorFile().GetFile()); + result.PutOutput(m_debugger.GetOutputFile().GetFile()); } const bool handle_events = true; diff --git a/llvm/include/llvm/ADT/StringRef.h b/llvm/include/llvm/ADT/StringRef.h --- a/llvm/include/llvm/ADT/StringRef.h +++ b/llvm/include/llvm/ADT/StringRef.h @@ -857,7 +857,7 @@ public: template constexpr StringLiteral(const char (&Str)[N]) -#if defined(__clang__) && __has_attribute(enable_if) +#if !__INTELLISENSE__ && defined(__clang__) && __has_attribute(enable_if) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wgcc-compat" __attribute((enable_if(__builtin_strlen(Str) == N - 1,