Author: zturner Date: Thu Nov 12 19:24:52 2015 New Revision: 252994 URL: http://llvm.org/viewvc/llvm-project?rev=252994&view=rev Log: Introduce a `PythonExceptionState` class.
This is a helper class which supports a number of features including exception to string formatting with backtrace handling and auto-restore of exception state upon scope exit. Additionally, unit tests are included to verify the feature set of the class. Added: lldb/trunk/source/Plugins/ScriptInterpreter/Python/PythonExceptionState.cpp lldb/trunk/source/Plugins/ScriptInterpreter/Python/PythonExceptionState.h lldb/trunk/unittests/ScriptInterpreter/Python/PythonExceptionStateTests.cpp Modified: lldb/trunk/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt lldb/trunk/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp lldb/trunk/unittests/ScriptInterpreter/Python/CMakeLists.txt Modified: lldb/trunk/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt?rev=252994&r1=252993&r2=252994&view=diff ============================================================================== --- lldb/trunk/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt (original) +++ lldb/trunk/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt Thu Nov 12 19:24:52 2015 @@ -1,4 +1,5 @@ add_lldb_library(lldbPluginScriptInterpreterPython PythonDataObjects.cpp + PythonExceptionState.cpp ScriptInterpreterPython.cpp ) Added: lldb/trunk/source/Plugins/ScriptInterpreter/Python/PythonExceptionState.cpp URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Plugins/ScriptInterpreter/Python/PythonExceptionState.cpp?rev=252994&view=auto ============================================================================== --- lldb/trunk/source/Plugins/ScriptInterpreter/Python/PythonExceptionState.cpp (added) +++ lldb/trunk/source/Plugins/ScriptInterpreter/Python/PythonExceptionState.cpp Thu Nov 12 19:24:52 2015 @@ -0,0 +1,195 @@ +//===-- PythonExceptionState.cpp --------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "lldb-python.h" +#include "PythonExceptionState.h" + +#include "llvm/ADT/StringRef.h" +#include "llvm/Support/raw_ostream.h" + +using namespace lldb_private; + +PythonExceptionState::PythonExceptionState(bool restore_on_exit) + : m_restore_on_exit(restore_on_exit) +{ + Acquire(restore_on_exit); +} + +PythonExceptionState::~PythonExceptionState() +{ + if (m_restore_on_exit) + Restore(); +} + +void +PythonExceptionState::Acquire(bool restore_on_exit) +{ + // If a state is already acquired, the user needs to decide whether they + // want to discard or restore it. Don't allow the potential silent + // loss of a valid state. + assert(!IsError()); + + if (!HasErrorOccurred()) + return; + + PyObject *py_type = nullptr; + PyObject *py_value = nullptr; + PyObject *py_traceback = nullptr; + PyErr_Fetch(&py_type, &py_value, &py_traceback); + // PyErr_Fetch clears the error flag. + assert(!HasErrorOccurred()); + + // Ownership of the objects returned by `PyErr_Fetch` is transferred + // to us. + m_type.Reset(PyRefType::Owned, py_type); + m_value.Reset(PyRefType::Owned, py_value); + m_traceback.Reset(PyRefType::Owned, py_traceback); +} + +void +PythonExceptionState::Restore() +{ + if (m_type.IsValid()) + { + // The documentation for PyErr_Restore says "Do not pass a null type and + // non-null value or traceback. So only restore if type was non-null + // to begin with. In this case we're passing ownership back to Python + // so release them all. + PyErr_Restore(m_type.release(), m_value.release(), m_traceback.release()); + } + + // After we restore, we should not hold onto the exception state. Demand that + // it be re-acquired. + Discard(); +} + +void +PythonExceptionState::Discard() +{ + m_type.Reset(); + m_value.Reset(); + m_traceback.Reset(); +} + +bool +PythonExceptionState::HasErrorOccurred() +{ + return PyErr_Occurred(); +} + +bool +PythonExceptionState::IsError() const +{ + return m_type.IsValid() || m_value.IsValid() || m_traceback.IsValid(); +} + +PythonObject +PythonExceptionState::GetType() const +{ + return m_type; +} + +PythonObject +PythonExceptionState::GetValue() const +{ + return m_value; +} + +PythonObject +PythonExceptionState::GetTraceback() const +{ + return m_traceback; +} + +std::string +PythonExceptionState::Format() const +{ + // Don't allow this function to modify the error state. + PythonExceptionState state(true); + + std::string backtrace = ReadBacktrace(); + if (!IsError()) + return std::string(); + + // It's possible that ReadPythonBacktrace generated another exception. + // If this happens we have to clear the exception, because otherwise + // PyObject_Str() will assert below. That's why we needed to do the + // save / restore at the beginning of this function. + PythonExceptionState bt_error_state(false); + + std::string error_string; + llvm::raw_string_ostream error_stream(error_string); + error_stream << m_value.Str().GetString() << "\n"; + + if (!bt_error_state.IsError()) + { + // If we were able to read the backtrace, just append it. + error_stream << backtrace << "\n"; + } + else + { + // Otherwise, append some information about why we were unable to + // obtain the backtrace. + PythonString bt_error = bt_error_state.GetValue().Str(); + error_stream << "An error occurred while retrieving the backtrace: " << bt_error.GetString() << "\n"; + } + return error_stream.str(); +} + +std::string +PythonExceptionState::ReadBacktrace() const +{ + PythonObject traceback_module; + PythonObject stringIO_module; + PythonObject stringIO_builder; + PythonObject stringIO_buffer; + PythonObject printTB; + PythonObject printTB_args; + PythonObject printTB_result; + PythonObject stringIO_getvalue; + PythonObject printTB_string; + + std::string retval("backtrace unavailable"); + + if (!m_traceback.IsAllocated()) + return retval; + + traceback_module.Reset(PyRefType::Owned, PyImport_ImportModule("traceback")); + stringIO_module.Reset(PyRefType::Owned, PyImport_ImportModule("StringIO")); + if (!traceback_module.IsAllocated() || !stringIO_module.IsAllocated()) + return retval; + + stringIO_builder.Reset(PyRefType::Owned, PyObject_GetAttrString(stringIO_module.get(), "StringIO")); + if (!stringIO_builder.IsAllocated()) + return retval; + + stringIO_buffer.Reset(PyRefType::Owned, PyObject_CallObject(stringIO_builder.get(), nullptr)); + if (!stringIO_buffer.IsAllocated()) + return retval; + + printTB.Reset(PyRefType::Owned, PyObject_GetAttrString(traceback_module.get(), "print_tb")); + if (!printTB.IsAllocated()) + return retval; + + printTB_args.Reset(PyRefType::Owned, Py_BuildValue("OOO", m_traceback.get(), Py_None, stringIO_buffer.get())); + printTB_result.Reset(PyRefType::Owned, PyObject_CallObject(printTB.get(), printTB_args.get())); + stringIO_getvalue.Reset(PyRefType::Owned, PyObject_GetAttrString(stringIO_buffer.get(), "getvalue")); + if (!stringIO_getvalue.IsAllocated()) + return retval; + + printTB_string.Reset(PyRefType::Owned, PyObject_CallObject(stringIO_getvalue.get(), nullptr)); + if (!printTB_string.IsAllocated()) + return retval; + + PythonString str(PyRefType::Borrowed, printTB_string.get()); + llvm::StringRef string_data(str.GetString()); + retval.assign(string_data.data(), string_data.size()); + + return retval; +} \ No newline at end of file Added: lldb/trunk/source/Plugins/ScriptInterpreter/Python/PythonExceptionState.h URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Plugins/ScriptInterpreter/Python/PythonExceptionState.h?rev=252994&view=auto ============================================================================== --- lldb/trunk/source/Plugins/ScriptInterpreter/Python/PythonExceptionState.h (added) +++ lldb/trunk/source/Plugins/ScriptInterpreter/Python/PythonExceptionState.h Thu Nov 12 19:24:52 2015 @@ -0,0 +1,63 @@ +//===-- PythonExceptionState.h ----------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_PYTHONEXCEPTIONSTATE_H +#define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_PYTHONEXCEPTIONSTATE_H + +#include "PythonDataObjects.h" + +namespace lldb_private +{ + +class PythonExceptionState +{ + public: + explicit PythonExceptionState(bool restore_on_exit); + ~PythonExceptionState(); + + void + Acquire(bool restore_on_exit); + + void + Restore(); + + void + Discard(); + + static bool + HasErrorOccurred(); + + bool + IsError() const; + + PythonObject + GetType() const; + + PythonObject + GetValue() const; + + PythonObject + GetTraceback() const; + + std::string + Format() const; + + private: + std::string + ReadBacktrace() const; + + bool m_restore_on_exit; + + PythonObject m_type; + PythonObject m_value; + PythonObject m_traceback; +}; +} + +#endif Modified: lldb/trunk/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp?rev=252994&r1=252993&r2=252994&view=diff ============================================================================== --- lldb/trunk/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp (original) +++ lldb/trunk/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp Thu Nov 12 19:24:52 2015 @@ -16,6 +16,7 @@ #include "lldb-python.h" #include "ScriptInterpreterPython.h" #include "PythonDataObjects.h" +#include "PythonExceptionState.h" #include <stdlib.h> #include <stdio.h> @@ -178,8 +179,6 @@ private: } -static std::string ReadPythonBacktrace(PythonObject py_backtrace); - ScriptInterpreterPython::Locker::Locker (ScriptInterpreterPython *py_interpreter, uint16_t on_entry, uint16_t on_leave, @@ -1245,37 +1244,9 @@ ScriptInterpreterPython::ExecuteMultiple } } - py_error.Reset(PyRefType::Borrowed, PyErr_Occurred()); - if (py_error.IsValid()) - { - // puts(in_string); - // _PyObject_Dump (py_error); - // PyErr_Print(); - // success = false; - - PyObject *py_type = nullptr; - PyObject *py_value = nullptr; - PyObject *py_traceback = nullptr; - PyErr_Fetch(&py_type, &py_value, &py_traceback); - - PythonObject type(PyRefType::Owned, py_type); - PythonObject value(PyRefType::Owned, py_value); - PythonObject traceback(PyRefType::Owned, py_traceback); - - // get the backtrace - std::string bt = ReadPythonBacktrace(traceback); - - if (value.IsAllocated()) - { - PythonString str = value.Str(); - llvm::StringRef value_str(str.GetString()); - error.SetErrorStringWithFormat("%s\n%s", value_str.str().c_str(), bt.c_str()); - } - else - error.SetErrorStringWithFormat("%s",bt.c_str()); - if (options.GetMaskoutErrors()) - PyErr_Clear(); - } + PythonExceptionState exception_state(!options.GetMaskoutErrors()); + if (exception_state.IsError()) + error.SetErrorString(exception_state.Format().c_str()); return error; } @@ -2377,58 +2348,6 @@ ScriptInterpreterPython::GetSyntheticVal return ret_val; } -static std::string -ReadPythonBacktrace(PythonObject py_backtrace) -{ - PythonObject traceback_module; - PythonObject stringIO_module; - PythonObject stringIO_builder; - PythonObject stringIO_buffer; - PythonObject printTB; - PythonObject printTB_args; - PythonObject printTB_result; - PythonObject stringIO_getvalue; - PythonObject printTB_string; - - std::string retval("backtrace unavailable"); - - if (!py_backtrace.IsAllocated()) - return retval; - - traceback_module.Reset(PyRefType::Owned, PyImport_ImportModule("traceback")); - stringIO_module.Reset(PyRefType::Owned, PyImport_ImportModule("StringIO")); - if (!traceback_module.IsAllocated() || !stringIO_module.IsAllocated()) - return retval; - - stringIO_builder.Reset(PyRefType::Owned, PyObject_GetAttrString(stringIO_module.get(), "StringIO")); - if (!stringIO_builder.IsAllocated()) - return retval; - - stringIO_buffer.Reset(PyRefType::Owned, PyObject_CallObject(stringIO_builder.get(), nullptr)); - if (!stringIO_buffer.IsAllocated()) - return retval; - - printTB.Reset(PyRefType::Owned, PyObject_GetAttrString(traceback_module.get(), "print_tb")); - if (!printTB.IsAllocated()) - return retval; - - printTB_args.Reset(PyRefType::Owned, Py_BuildValue("OOO", py_backtrace.get(), Py_None, stringIO_buffer.get())); - printTB_result.Reset(PyRefType::Owned, PyObject_CallObject(printTB.get(), printTB_args.get())); - stringIO_getvalue.Reset(PyRefType::Owned, PyObject_GetAttrString(stringIO_buffer.get(), "getvalue")); - if (!stringIO_getvalue.IsAllocated()) - return retval; - - printTB_string.Reset(PyRefType::Owned, PyObject_CallObject(stringIO_getvalue.get(), nullptr)); - if (!printTB_string.IsAllocated()) - return retval; - - PythonString str(PyRefType::Borrowed, printTB_string.get()); - llvm::StringRef string_data(str.GetString()); - retval.assign(string_data.data(), string_data.size()); - - return retval; -} - bool ScriptInterpreterPython::RunScriptFormatKeyword (const char* impl_function, Process* process, Modified: lldb/trunk/unittests/ScriptInterpreter/Python/CMakeLists.txt URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/unittests/ScriptInterpreter/Python/CMakeLists.txt?rev=252994&r1=252993&r2=252994&view=diff ============================================================================== --- lldb/trunk/unittests/ScriptInterpreter/Python/CMakeLists.txt (original) +++ lldb/trunk/unittests/ScriptInterpreter/Python/CMakeLists.txt Thu Nov 12 19:24:52 2015 @@ -1,5 +1,6 @@ add_lldb_unittest(ScriptInterpreterPythonTests PythonDataObjectsTests.cpp + PythonExceptionStateTests.cpp PythonTestSuite.cpp ) Added: lldb/trunk/unittests/ScriptInterpreter/Python/PythonExceptionStateTests.cpp URL: http://llvm.org/viewvc/llvm-project/lldb/trunk/unittests/ScriptInterpreter/Python/PythonExceptionStateTests.cpp?rev=252994&view=auto ============================================================================== --- lldb/trunk/unittests/ScriptInterpreter/Python/PythonExceptionStateTests.cpp (added) +++ lldb/trunk/unittests/ScriptInterpreter/Python/PythonExceptionStateTests.cpp Thu Nov 12 19:24:52 2015 @@ -0,0 +1,121 @@ +//===-- PythonExceptionStateTest.cpp ------------------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "gtest/gtest.h" + +#include "Plugins/ScriptInterpreter/Python/lldb-python.h" +#include "Plugins/ScriptInterpreter/Python/PythonDataObjects.h" +#include "Plugins/ScriptInterpreter/Python/PythonExceptionState.h" +#include "Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h" + +#include "PythonTestSuite.h" + +using namespace lldb_private; + +class PythonExceptionStateTest : public PythonTestSuite +{ + public: + protected: + void + RaiseException() + { + PyErr_SetString(PyExc_RuntimeError, "PythonExceptionStateTest test error"); + } +}; + +TEST_F(PythonExceptionStateTest, TestExceptionStateChecking) +{ + PyErr_Clear(); + EXPECT_FALSE(PythonExceptionState::HasErrorOccurred()); + + RaiseException(); + EXPECT_TRUE(PythonExceptionState::HasErrorOccurred()); + + PyErr_Clear(); +} + +TEST_F(PythonExceptionStateTest, TestAcquisitionSemantics) +{ + PyErr_Clear(); + PythonExceptionState no_error(false); + EXPECT_FALSE(no_error.IsError()); + EXPECT_FALSE(PythonExceptionState::HasErrorOccurred()); + + PyErr_Clear(); + RaiseException(); + PythonExceptionState error(false); + EXPECT_TRUE(error.IsError()); + EXPECT_FALSE(PythonExceptionState::HasErrorOccurred()); + error.Discard(); + + PyErr_Clear(); + RaiseException(); + error.Acquire(false); + EXPECT_TRUE(error.IsError()); + EXPECT_FALSE(PythonExceptionState::HasErrorOccurred()); + + PyErr_Clear(); +} + +TEST_F(PythonExceptionStateTest, TestDiscardSemantics) +{ + PyErr_Clear(); + + // Test that discarding an exception does not restore the exception + // state even when auto-restore==true is set + RaiseException(); + PythonExceptionState error(true); + EXPECT_TRUE(error.IsError()); + EXPECT_FALSE(PythonExceptionState::HasErrorOccurred()); + + error.Discard(); + EXPECT_FALSE(error.IsError()); + EXPECT_FALSE(PythonExceptionState::HasErrorOccurred()); +} + +TEST_F(PythonExceptionStateTest, TestManualRestoreSemantics) +{ + PyErr_Clear(); + RaiseException(); + PythonExceptionState error(false); + EXPECT_TRUE(error.IsError()); + EXPECT_FALSE(PythonExceptionState::HasErrorOccurred()); + + error.Restore(); + EXPECT_FALSE(error.IsError()); + EXPECT_TRUE(PythonExceptionState::HasErrorOccurred()); + + PyErr_Clear(); +} + +TEST_F(PythonExceptionStateTest, TestAutoRestoreSemantics) +{ + PyErr_Clear(); + // Test that using the auto-restore flag correctly restores the exception + // state on destruction, and not using the auto-restore flag correctly + // does NOT restore the state on destruction. + { + RaiseException(); + PythonExceptionState error(false); + EXPECT_TRUE(error.IsError()); + EXPECT_FALSE(PythonExceptionState::HasErrorOccurred()); + } + EXPECT_FALSE(PythonExceptionState::HasErrorOccurred()); + + PyErr_Clear(); + { + RaiseException(); + PythonExceptionState error(true); + EXPECT_TRUE(error.IsError()); + EXPECT_FALSE(PythonExceptionState::HasErrorOccurred()); + } + EXPECT_TRUE(PythonExceptionState::HasErrorOccurred()); + + PyErr_Clear(); +} _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits