From 085328eeeeba63ab3ef2d59f1bb8fa83ca069d33 Mon Sep 17 00:00:00 2001 From: Lawrence D'Anna Date: Tue, 8 Oct 2019 17:56:18 +0000 Subject: exception handling in PythonDataObjects. Summary: Python APIs nearly all can return an exception. They do this by returning NULL, or -1, or some such value and setting the exception state with PyErr_Set*(). Exceptions must be handled before further python API functions are called. Failure to do so will result in asserts on debug builds of python. It will also sometimes, but not usually result in crashes of release builds. Nearly everything in PythonDataObjects.h needs to be updated to account for this. This patch doesn't fix everything, but it does introduce some new methods using Expected<> return types that are safe to use. split off from https://reviews.llvm.org/D68188 Reviewers: JDevlieghere, jasonmolenda, labath, zturner Reviewed By: labath Subscribers: lldb-commits Tags: #lldb Differential Revision: https://reviews.llvm.org/D68547 llvm-svn: 374094 --- .../ScriptInterpreter/Python/PythonDataObjects.cpp | 155 +++++++++++++++++---- 1 file changed, 130 insertions(+), 25 deletions(-) (limited to 'lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp') diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp index feb0399..13fb01ff 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp @@ -18,6 +18,7 @@ #include "lldb/Host/File.h" #include "lldb/Host/FileSystem.h" #include "lldb/Interpreter/ScriptInterpreter.h" +#include "lldb/Utility/Log.h" #include "lldb/Utility/Stream.h" #include "llvm/ADT/StringSwitch.h" @@ -28,6 +29,22 @@ using namespace lldb_private; using namespace lldb; +using namespace lldb_private::python; +using llvm::Error; +using llvm::Expected; + +template <> Expected python::As(Expected &&obj) { + if (!obj) + return obj.takeError(); + return obj.get().IsTrue(); +} + +template <> +Expected python::As(Expected &&obj) { + if (!obj) + return obj.takeError(); + return obj.get().AsLongLong(); +} void StructuredPythonObject::Serialize(llvm::json::OStream &s) const { s.value(llvm::formatv("Python Obj: {0:X}", GetValue()).str()); @@ -167,12 +184,6 @@ PythonObject PythonObject::GetAttributeValue(llvm::StringRef attr) const { PyObject_GetAttr(m_py_obj, py_attr.get())); } -bool PythonObject::IsNone() const { return m_py_obj == Py_None; } - -bool PythonObject::IsValid() const { return m_py_obj != nullptr; } - -bool PythonObject::IsAllocated() const { return IsValid() && !IsNone(); } - StructuredData::ObjectSP PythonObject::CreateStructuredObject() const { switch (GetObjectType()) { case PyObjectType::Dictionary: @@ -334,6 +345,17 @@ StructuredData::StringSP PythonByteArray::CreateStructuredString() const { // PythonString +Expected PythonString::FromUTF8(llvm::StringRef string) { +#if PY_MAJOR_VERSION >= 3 + PyObject *str = PyUnicode_FromStringAndSize(string.data(), string.size()); +#else + PyObject *str = PyString_FromStringAndSize(string.data(), string.size()); +#endif + if (!str) + return llvm::make_error(); + return Take(str); +} + PythonString::PythonString(PyRefType type, PyObject *py_obj) : PythonObject() { Reset(type, py_obj); // Use "Reset()" to ensure that py_obj is a string } @@ -342,10 +364,6 @@ PythonString::PythonString(llvm::StringRef string) : PythonObject() { SetString(string); } -PythonString::PythonString(const char *string) : PythonObject() { - SetString(llvm::StringRef(string)); -} - PythonString::PythonString() : PythonObject() {} PythonString::~PythonString() {} @@ -376,8 +394,12 @@ void PythonString::Reset(PyRefType type, PyObject *py_obj) { // In Python 2, Don't store PyUnicode objects directly, because we need // access to their underlying character buffers which Python 2 doesn't // provide. - if (PyUnicode_Check(py_obj)) - result.Reset(PyRefType::Owned, PyUnicode_AsUTF8String(result.get())); + if (PyUnicode_Check(py_obj)) { + PyObject *s = PyUnicode_AsUTF8String(result.get()); + if (s == NULL) + PyErr_Clear(); + result.Reset(PyRefType::Owned, s); + } #endif // Calling PythonObject::Reset(const PythonObject&) will lead to stack // overflow since it calls back into the virtual implementation. @@ -385,8 +407,17 @@ void PythonString::Reset(PyRefType type, PyObject *py_obj) { } llvm::StringRef PythonString::GetString() const { + auto s = AsUTF8(); + if (!s) { + llvm::consumeError(s.takeError()); + return llvm::StringRef(""); + } + return s.get(); +} + +Expected PythonString::AsUTF8() const { if (!IsValid()) - return llvm::StringRef(); + return nullDeref(); Py_ssize_t size; const char *data; @@ -394,10 +425,16 @@ llvm::StringRef PythonString::GetString() const { #if PY_MAJOR_VERSION >= 3 data = PyUnicode_AsUTF8AndSize(m_py_obj, &size); #else - char *c; - PyString_AsStringAndSize(m_py_obj, &c, &size); + char *c = NULL; + int r = PyString_AsStringAndSize(m_py_obj, &c, &size); + if (r < 0) + c = NULL; data = c; #endif + + if (!data) + return exception(); + return llvm::StringRef(data, size); } @@ -413,13 +450,13 @@ size_t PythonString::GetSize() const { } void PythonString::SetString(llvm::StringRef string) { -#if PY_MAJOR_VERSION >= 3 - PyObject *unicode = PyUnicode_FromStringAndSize(string.data(), string.size()); - PythonObject::Reset(PyRefType::Owned, unicode); -#else - PyObject *str = PyString_FromStringAndSize(string.data(), string.size()); - PythonObject::Reset(PyRefType::Owned, str); -#endif + auto s = FromUTF8(string); + if (!s) { + llvm::consumeError(s.takeError()); + Reset(); + } else { + PythonObject::Reset(std::move(s.get())); + } } StructuredData::StringSP PythonString::CreateStructuredString() const { @@ -826,9 +863,23 @@ PythonModule PythonModule::AddModule(llvm::StringRef module) { return PythonModule(PyRefType::Borrowed, PyImport_AddModule(str.c_str())); } -PythonModule PythonModule::ImportModule(llvm::StringRef module) { - std::string str = module.str(); - return PythonModule(PyRefType::Owned, PyImport_ImportModule(str.c_str())); +Expected PythonModule::Import(const char *name) { + PyObject *mod = PyImport_ImportModule(name); + if (!mod) + return exception(); + return Take(mod); +} + +Expected PythonModule::Get(const char *name) { + if (!IsValid()) + return nullDeref(); + PyObject *dict = PyModule_GetDict(m_py_obj); + if (!dict) + return exception(); + PyObject *item = PyDict_GetItemString(dict, name); + if (!item) + return exception(); + return Retain(item); } bool PythonModule::Check(PyObject *py_obj) { @@ -1045,4 +1096,58 @@ FileUP PythonFile::GetUnderlyingFile() const { return file; } +const char *PythonException::toCString() const { + if (!m_repr_bytes) + return "unknown exception"; + return PyBytes_AS_STRING(m_repr_bytes); +} + +PythonException::PythonException(const char *caller) { + assert(PyErr_Occurred()); + m_exception_type = m_exception = m_traceback = m_repr_bytes = NULL; + PyErr_Fetch(&m_exception_type, &m_exception, &m_traceback); + PyErr_NormalizeException(&m_exception_type, &m_exception, &m_traceback); + PyErr_Clear(); + if (m_exception) { + PyObject *repr = PyObject_Repr(m_exception); + if (repr) { + m_repr_bytes = PyUnicode_AsEncodedString(repr, "utf-8", nullptr); + if (!m_repr_bytes) { + PyErr_Clear(); + } + Py_XDECREF(repr); + } else { + PyErr_Clear(); + } + } + Log *log = GetLogIfAllCategoriesSet(LIBLLDB_LOG_SCRIPT); + if (caller) + LLDB_LOGF(log, "%s failed with exception: %s", caller, toCString()); + else + LLDB_LOGF(log, "python exception: %s", toCString()); +} +void PythonException::Restore() { + if (m_exception_type && m_exception) { + PyErr_Restore(m_exception_type, m_exception, m_traceback); + } else { + PyErr_SetString(PyExc_Exception, toCString()); + } + m_exception_type = m_exception = m_traceback = NULL; +} + +PythonException::~PythonException() { + Py_XDECREF(m_exception_type); + Py_XDECREF(m_exception); + Py_XDECREF(m_traceback); + Py_XDECREF(m_repr_bytes); +} + +void PythonException::log(llvm::raw_ostream &OS) const { OS << toCString(); } + +std::error_code PythonException::convertToErrorCode() const { + return llvm::inconvertibleErrorCode(); +} + +char PythonException::ID = 0; + #endif -- cgit v1.1