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 @@ -411,6 +411,8 @@ size_t PrintfVarArg(const char *format, va_list args) override; llvm::Expected GetOptions() const override; + static FILE *ReleaseFILE(NativeFile &&file); + static char ID; virtual bool isA(const void *classID) const override { return classID == &ID || File::isA(classID); 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 @@ -845,6 +845,7 @@ def i(sbf): for i in range(10): f = sbf.GetFile() + self.assertEqual(f.mode, "w") yield f sbf = lldb.SBFile.Create(f, borrow=True) yield sbf 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 @@ -304,13 +304,25 @@ return m_stream; } +FILE *NativeFile::ReleaseFILE(NativeFile &&file) { + FILE *stream = nullptr; + file.GetStream(); + if (file.m_own_stream) { + stream = file.m_stream; + file.m_own_stream = false; + file.m_stream = nullptr; + } + file.Close(); + return stream; +} + Status NativeFile::Close() { Status error; if (StreamIsValid()) { if (m_own_stream) { if (::fclose(m_stream) == EOF) error.SetErrorToErrno(); - } else { + } else if (m_options & eOpenOptionWrite) { if (::fflush(m_stream) == EOF) error.SetErrorToErrno(); } 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 @@ -189,6 +189,14 @@ "key not in dict"); } +#if PY_MAJOR_VERSION < 3 +// The python 2 API declares some arguments as char* that should +// be const char *, but it doesn't actually modify them. +inline char *py2_const_cast(const char *s) { return const_cast(s); } +#else +inline const char *py2_const_cast(const char *s) { return s; } +#endif + enum class PyInitialValue { Invalid, Empty }; template struct PythonFormat; @@ -309,16 +317,6 @@ StructuredData::ObjectSP CreateStructuredObject() const; -protected: - -#if PY_MAJOR_VERSION < 3 - // The python 2 API declares some arguments as char* that should - // be const char *, but it doesn't actually modify them. - static char *py2_const_cast(const char *s) { return const_cast(s); } -#else - static const char *py2_const_cast(const char *s) { return s; } -#endif - public: template llvm::Expected CallMethod(const char *name, 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 @@ -1502,12 +1502,28 @@ file_obj = PyFile_FromFd(file.GetDescriptor(), nullptr, mode, -1, nullptr, "ignore", nullptr, 0); #else - // Read through the Python source, doesn't seem to modify these strings - char *cmode = const_cast(mode); - // We pass ::flush instead of ::fclose here so we borrow the FILE* -- - // the lldb_private::File still owns it. - file_obj = - PyFile_FromFile(file.GetStream(), const_cast(""), cmode, ::fflush); + // It's more or less safe to let a python program borrow a file descriptor. + // If they let it dangle and then use it, they'll probably get an exception. + // The worst that can happen is they'll wind up doing IO on the wrong + // descriptor. But it would be very unsafe to let a python program borrow + // a FILE*. They could actually crash the program just by keeping a + // reference to it around. Since in python 2 we must have a FILE* and + // not a descriptor, we dup the descriptor and fdopen a new FILE* to it + // so python can have something it can own safely. + auto opts = File::GetOptionsFromMode(mode); + if (!opts) + return opts.takeError(); + int fd = file.GetDescriptor(); + if (!File::DescriptorIsValid(fd)) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "File has no file descriptor"); + NativeFile dupfile(fd, opts.get(), false); + FILE *stream = NativeFile::ReleaseFILE(std::move(dupfile)); + if (!stream) + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "could not create FILE* stream"); + file_obj = PyFile_FromFile(stream, py2_const_cast(""), py2_const_cast(mode), + ::fclose); #endif if (!file_obj)