[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