blob: c9d834ce6868bbc479ed1403b2730d743dc1b27a [file] [log] [blame]
//===-- PythonExceptionState.cpp --------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
#ifndef LLDB_DISABLE_PYTHON
// LLDB Python header must be included first
#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);
m_restore_on_exit = restore_on_exit;
}
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();
}
void PythonExceptionState::Reset() {
if (m_restore_on_exit)
Restore();
else
Discard();
}
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 {
std::string retval("backtrace unavailable");
auto traceback_module = PythonModule::ImportModule("traceback");
#if PY_MAJOR_VERSION >= 3
auto stringIO_module = PythonModule::ImportModule("io");
#else
auto stringIO_module = PythonModule::ImportModule("StringIO");
#endif
if (!m_traceback.IsAllocated())
return retval;
if (!traceback_module.IsAllocated() || !stringIO_module.IsAllocated())
return retval;
auto stringIO_builder =
stringIO_module.ResolveName<PythonCallable>("StringIO");
if (!stringIO_builder.IsAllocated())
return retval;
auto stringIO_buffer = stringIO_builder();
if (!stringIO_buffer.IsAllocated())
return retval;
auto printTB = traceback_module.ResolveName<PythonCallable>("print_tb");
if (!printTB.IsAllocated())
return retval;
auto printTB_result =
printTB(m_traceback.get(), Py_None, stringIO_buffer.get());
auto stringIO_getvalue =
stringIO_buffer.ResolveName<PythonCallable>("getvalue");
if (!stringIO_getvalue.IsAllocated())
return retval;
auto printTB_string = stringIO_getvalue().AsType<PythonString>();
if (!printTB_string.IsAllocated())
return retval;
llvm::StringRef string_data(printTB_string.GetString());
retval.assign(string_data.data(), string_data.size());
return retval;
}
#endif