[Lldb-commits] [lldb] r252994 - Introduce a `PythonExceptionState` class.
Zachary Turner via lldb-commits
lldb-commits at lists.llvm.org
Thu Nov 12 17:24:52 PST 2015
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();
+}
More information about the lldb-commits
mailing list