aboutsummaryrefslogtreecommitdiffstats
path: root/source/Plugins/ScriptInterpreter/Python/PythonExceptionState.cpp
blob: c9d834ce6868bbc479ed1403b2730d743dc1b27a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
//===-- 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