diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h --- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -65,6 +65,9 @@ bool GetSetLLDBGlobals() const { return m_set_lldb_globals; } + /* If this is true then any exceptions raised by the script will be + * cleared with PyErr_Clear(). If false then they will be left for + * the caller to clean up */ bool GetMaskoutErrors() const { return m_maskout_errors; } ExecuteScriptOptions &SetEnableIO(bool enable) { diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt b/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt --- a/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt +++ b/lldb/source/Plugins/ScriptInterpreter/Python/CMakeLists.txt @@ -5,7 +5,6 @@ add_lldb_library(lldbPluginScriptInterpreterPython PLUGIN PythonDataObjects.cpp - PythonExceptionState.cpp ScriptInterpreterPython.cpp LINK_LIBS diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h --- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h @@ -151,6 +151,20 @@ return std::move(thing); } +inline llvm::Error nullDeref() { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "A NULL PyObject* was dereferenced"); +} + +inline llvm::Error exception(const char *s = nullptr) { + return llvm::make_error(s); +} + +inline llvm::Error keyError() { + return llvm::createStringError(llvm::inconvertibleErrorCode(), + "key not in dict"); +} + } // namespace python enum class PyInitialValue { Invalid, Empty }; @@ -167,6 +181,11 @@ static auto get(long long value) { return value; } }; +template <> struct PythonFormat { + static constexpr char format = 'O'; + static auto get(PyObject *value) { return value; } +}; + template struct PythonFormat< T, typename std::enable_if::value>::type> { @@ -197,6 +216,7 @@ llvm::StringRef ref = twine.toNullTerminatedStringRef(storage); cstr = ref.data(); } + const char *str() const { return cstr; } }; @@ -204,8 +224,14 @@ public: PythonObject() : m_py_obj(nullptr) {} - PythonObject(PyRefType type, PyObject *py_obj) : m_py_obj(nullptr) { - Reset(type, py_obj); + PythonObject(PyRefType type, PyObject *py_obj) { + m_py_obj = py_obj; + // If this is a borrowed reference, we need to convert it to + // an owned reference by incrementing it. If it is an owned + // reference (for example the caller allocated it with PyDict_New() + // then we must *not* increment it. + if (m_py_obj && Py_IsInitialized() && type == PyRefType::Borrowed) + Py_XINCREF(m_py_obj); } PythonObject(const PythonObject &rhs) @@ -224,23 +250,6 @@ m_py_obj = nullptr; } - void Reset(PyRefType type, PyObject *py_obj) { - if (py_obj == m_py_obj) - return; - - if (Py_IsInitialized()) - Py_XDECREF(m_py_obj); - - m_py_obj = py_obj; - - // If this is a borrowed reference, we need to convert it to - // an owned reference by incrementing it. If it is an owned - // reference (for example the caller allocated it with PyDict_New() - // then we must *not* increment it. - if (m_py_obj && Py_IsInitialized() && type == PyRefType::Borrowed) - Py_XINCREF(m_py_obj); - } - void Dump() const { if (m_py_obj) _PyObject_Dump(m_py_obj); @@ -306,17 +315,6 @@ StructuredData::ObjectSP CreateStructuredObject() const; protected: - static llvm::Error nullDeref() { - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "A NULL PyObject* was dereferenced"); - } - static llvm::Error exception(const char *s = nullptr) { - return llvm::make_error(s); - } - static llvm::Error keyError() { - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "key not in dict"); - } #if PY_MAJOR_VERSION < 3 // The python 2 API declares some arguments as char* that should @@ -330,6 +328,7 @@ template llvm::Expected CallMethod(const char *name, const T &... t) const { + using namespace python; const char format[] = {'(', PythonFormat::format..., ')', 0}; PyObject *obj = PyObject_CallMethod(m_py_obj, py2_const_cast(name), @@ -341,6 +340,7 @@ template llvm::Expected Call(const T &... t) const { + using namespace python; const char format[] = {'(', PythonFormat::format..., ')', 0}; PyObject *obj = PyObject_CallFunction(m_py_obj, py2_const_cast(format), PythonFormat::get(t)...); @@ -350,6 +350,7 @@ } llvm::Expected GetAttribute(CStringArg name) const { + using namespace python; if (!m_py_obj) return nullDeref(); PyObject *obj = PyObject_GetAttrString(m_py_obj, name.str()); @@ -359,6 +360,7 @@ } llvm::Expected IsTrue() { + using namespace python; if (!m_py_obj) return nullDeref(); int r = PyObject_IsTrue(m_py_obj); @@ -368,6 +370,7 @@ } llvm::Expected AsLongLong() { + using namespace python; if (!m_py_obj) return nullDeref(); assert(!PyErr_Occurred()); @@ -378,6 +381,7 @@ } llvm::Expected IsInstance(const PythonObject &cls) { + using namespace python; if (!m_py_obj || !cls.IsValid()) return nullDeref(); int r = PyObject_IsInstance(m_py_obj, cls.get()); @@ -418,16 +422,12 @@ // This can be eliminated once we drop python 2 support. static void Convert(PyRefType &type, PyObject *&py_obj) {} - void Reset() { PythonObject::Reset(); } - - void Reset(PyRefType type, PyObject *py_obj) = delete; - TypedPythonObject(PyRefType type, PyObject *py_obj) { if (!py_obj) return; T::Convert(type, py_obj); if (T::Check(py_obj)) - PythonObject::Reset(type, py_obj); + PythonObject::operator=(PythonObject(type, py_obj)); else if (type == PyRefType::Owned) Py_DECREF(py_obj); } @@ -699,6 +699,8 @@ ~PythonException(); void log(llvm::raw_ostream &OS) const override; std::error_code convertToErrorCode() const override; + bool Matches(PyObject *exc) const; + std::string ReadBacktrace() const; }; // This extracts the underlying T out of an Expected and returns it. @@ -742,8 +744,55 @@ llvm::consumeError(expected.takeError()); return T(); } + +llvm::Expected runStringOneLine(CStringArg string, + const PythonDictionary &globals, + const PythonDictionary &locals); + +llvm::Expected runStringMultiLine(CStringArg string, + const PythonDictionary &globals, + const PythonDictionary &locals); + } // namespace python +/* Sometimes the best way to interact with a python interpreter is + * to run some python code. You construct a PythonScript with + * script string and a function name, and you get a C++ callable + * object that calls the python function. + * + * Example: + * + * const char script[] = R"( + * def foo(x, y): + * .... + * )"; + * + * Expected cpp_foo_wrapper(PythonObject x, PythonObject y) { + * // global is protected by the GIL + * static PythonScript foo(script, "foo") + * return foo(x, y); + * } + */ +class PythonScript { + const char *script; + const char *function_name; + PythonCallable function; + + llvm::Error Init(); + +public: + PythonScript(const char *script, const char *name) + : script(script), function_name(name), function() {} + + template + llvm::Expected operator()(Args &&... args) { + llvm::Error e = Init(); + if (e) + return std::move(e); + return function.Call(std::forward(args)...); + } +}; + } // namespace lldb_private #endif diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp --- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp @@ -260,8 +260,7 @@ void PythonBytes::SetBytes(llvm::ArrayRef bytes) { const char *data = reinterpret_cast(bytes.data()); - PyObject *py_bytes = PyBytes_FromStringAndSize(data, bytes.size()); - PythonObject::Reset(PyRefType::Owned, py_bytes); + *this = Take(PyBytes_FromStringAndSize(data, bytes.size())); } StructuredData::StringSP PythonBytes::CreateStructuredString() const { @@ -485,7 +484,7 @@ } void PythonInteger::SetInteger(int64_t value) { - PythonObject::Reset(PyRefType::Owned, PyLong_FromLongLong(value)); + *this = Take(PyLong_FromLongLong(value)); } StructuredData::IntegerSP PythonInteger::CreateStructuredInteger() const { @@ -509,7 +508,7 @@ } void PythonBoolean::SetValue(bool value) { - PythonObject::Reset(PyRefType::Owned, PyBool_FromLong(value)); + *this = Take(PyBool_FromLong(value)); } StructuredData::BooleanSP PythonBoolean::CreateStructuredBoolean() const { @@ -852,27 +851,8 @@ #if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 3 // this global is protected by the GIL - static PythonCallable get_arg_info; - - if (!get_arg_info.IsValid()) { - PythonDictionary globals(PyInitialValue::Empty); - - auto builtins = PythonModule::BuiltinsModule(); - Error error = globals.SetItem("__builtins__", builtins); - if (error) - return std::move(error); - PyObject *o = PyRun_String(get_arg_info_script, Py_file_input, - globals.get(), globals.get()); - if (!o) - return exception(); - Take(o); - auto function = As(globals.GetItem("get_arg_info")); - if (!function) - return function.takeError(); - get_arg_info = std::move(function.get()); - } - - Expected pyarginfo = get_arg_info.Call(*this); + static PythonScript get_arg_info(get_arg_info_script, "get_arg_info"); + Expected pyarginfo = get_arg_info(*this); if (!pyarginfo) return pyarginfo.takeError(); result.count = cantFail(As(pyarginfo.get().GetAttribute("count"))); @@ -1055,6 +1035,39 @@ return llvm::inconvertibleErrorCode(); } +bool PythonException::Matches(PyObject *exc) const { + return PyErr_GivenExceptionMatches(m_exception_type, exc); +} + +const char read_exception_script[] = R"( +import sys +from traceback import print_exception +if sys.version_info.major < 3: + from StringIO import StringIO +else: + from io import StringIO +def read_exception(exc_type, exc_value, tb): + f = StringIO() + print_exception(exc_type, exc_value, tb, file=f) + return f.getvalue() +)"; + +std::string PythonException::ReadBacktrace() const { + + // global is protected by the GIL + static PythonScript read_exception(read_exception_script, "read_exception"); + + Expected backtrace = As( + read_exception(m_exception_type, m_exception, m_traceback)); + + if (!backtrace) { + llvm::consumeError(backtrace.takeError()); + return "backtrace unavailable"; + } + + return std::move(backtrace.get()); +} + char PythonException::ID = 0; llvm::Expected @@ -1495,4 +1508,64 @@ return Take(file_obj); } +Error PythonScript::Init() { + if (!function.IsValid()) { + PythonDictionary globals(PyInitialValue::Empty); + + auto builtins = PythonModule::BuiltinsModule(); + Error error = globals.SetItem("__builtins__", builtins); + if (error) + return error; + PyObject *o = + PyRun_String(script, Py_file_input, globals.get(), globals.get()); + if (!o) + return exception(); + Take(o); + auto f = As(globals.GetItem(function_name)); + if (!f) + return f.takeError(); + function = std::move(f.get()); + } + return Error::success(); +} + +llvm::Expected +python::runStringOneLine(CStringArg string, const PythonDictionary &globals, + const PythonDictionary &locals) { + if (!globals.IsValid() || !locals.IsValid()) + return nullDeref(); + + PyObject *code = Py_CompileString(string.str(), "", Py_eval_input); + if (!code) { + PyErr_Clear(); + code = Py_CompileString(string.str(), "", Py_single_input); + } + if (!code) + return exception(); + auto code_ref = Take(code); + +#if PY_MAJOR_VERSION < 3 + PyObject *result = PyEval_EvalCode((PyCodeObject*)code, globals.get(), locals.get()); +#else + PyObject *result = PyEval_EvalCode(code, globals.get(), locals.get()); +#endif + + if (!result) + return exception(); + + return Take(result); +} + +llvm::Expected +python::runStringMultiLine(CStringArg string, const PythonDictionary &globals, + const PythonDictionary &locals) { + if (!globals.IsValid() || !locals.IsValid()) + return nullDeref(); + PyObject *result = + PyRun_String(string.str(), Py_file_input, globals.get(), locals.get()); + if (!result) + return exception(); + return Take(result); +} + #endif diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonExceptionState.h b/lldb/source/Plugins/ScriptInterpreter/Python/PythonExceptionState.h deleted file mode 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonExceptionState.h +++ /dev/null @@ -1,56 +0,0 @@ -//===-- PythonExceptionState.h ----------------------------------*- 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_PLUGINS_SCRIPTINTERPRETER_PYTHON_PYTHONEXCEPTIONSTATE_H -#define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_PYTHONEXCEPTIONSTATE_H - -#ifndef LLDB_DISABLE_PYTHON - -#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(); - - void Reset(); - - 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 - -#endif diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonExceptionState.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/PythonExceptionState.cpp deleted file mode 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonExceptionState.cpp +++ /dev/null @@ -1,169 +0,0 @@ -//===-- 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("StringIO"); - if (!stringIO_builder.IsAllocated()) - return retval; - - auto stringIO_buffer = stringIO_builder(); - if (!stringIO_buffer.IsAllocated()) - return retval; - - auto printTB = traceback_module.ResolveName("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("getvalue"); - if (!stringIO_getvalue.IsAllocated()) - return retval; - - auto printTB_string = stringIO_getvalue().AsType(); - if (!printTB_string.IsAllocated()) - return retval; - - llvm::StringRef string_data(printTB_string.GetString()); - retval.assign(string_data.data(), string_data.size()); - - return retval; -} - -#endif diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -16,7 +16,6 @@ #include "lldb-python.h" #include "PythonDataObjects.h" -#include "PythonExceptionState.h" #include "ScriptInterpreterPythonImpl.h" #include "lldb/API/SBFrame.h" @@ -56,6 +55,7 @@ using namespace lldb; using namespace lldb_private; using namespace lldb_private::python; +using llvm::Expected; // Defined in the SWIG source file #if PY_MAJOR_VERSION >= 3 @@ -747,9 +747,9 @@ return true; } -PythonObject &ScriptInterpreterPythonImpl::GetMainModule() { +PythonModule &ScriptInterpreterPythonImpl::GetMainModule() { if (!m_main_module.IsValid()) - m_main_module.Reset(PyRefType::Borrowed, PyImport_AddModule("__main__")); + m_main_module = unwrapIgnoringErrors(PythonModule::Import("__main__")); return m_main_module; } @@ -1030,6 +1030,7 @@ "can't interrupt"); return false; } + bool ScriptInterpreterPythonImpl::ExecuteOneLineWithReturn( llvm::StringRef in_string, ScriptInterpreter::ScriptReturnType return_type, void *ret_value, const ExecuteScriptOptions &options) { @@ -1040,151 +1041,111 @@ Locker::NoSTDIN, Locker::FreeAcquiredLock | Locker::TearDownSession); - PythonObject py_return; - PythonObject &main_module = GetMainModule(); - PythonDictionary globals(PyRefType::Borrowed, - PyModule_GetDict(main_module.get())); - PythonObject py_error; - bool ret_success = false; - int success; + PythonModule &main_module = GetMainModule(); + PythonDictionary globals = main_module.GetDictionary(); PythonDictionary locals = GetSessionDictionary(); - - if (!locals.IsValid()) { + if (!locals.IsValid()) locals = unwrapIgnoringErrors( As(globals.GetAttribute(m_dictionary_name))); - } - if (!locals.IsValid()) locals = globals; - py_error.Reset(PyRefType::Borrowed, PyErr_Occurred()); - if (py_error.IsValid()) - PyErr_Clear(); - - std::string as_string = in_string.str(); - { // scope for PythonInputReaderManager - // PythonInputReaderManager py_input(options.GetEnableIO() ? this : NULL); - py_return.Reset(PyRefType::Owned, - PyRun_String(as_string.c_str(), Py_eval_input, - globals.get(), locals.get())); - if (!py_return.IsValid()) { - py_error.Reset(PyRefType::Borrowed, PyErr_Occurred()); - if (py_error.IsValid()) - PyErr_Clear(); - - py_return.Reset(PyRefType::Owned, - PyRun_String(as_string.c_str(), Py_single_input, - globals.get(), locals.get())); - } + Expected maybe_py_return = + runStringOneLine(in_string, globals, locals); + + if (!maybe_py_return) { + llvm::handleAllErrors( + maybe_py_return.takeError(), + [&](PythonException &E) { + E.Restore(); + if (options.GetMaskoutErrors()) { + if (E.Matches(PyExc_SyntaxError)) { + PyErr_Print(); + } + PyErr_Clear(); + } + }, + [](const llvm::ErrorInfoBase &E) {}); + return false; } - if (py_return.IsValid()) { - switch (return_type) { - case eScriptReturnTypeCharPtr: // "char *" - { - const char format[3] = "s#"; - success = PyArg_Parse(py_return.get(), format, (char **)ret_value); - break; - } - case eScriptReturnTypeCharStrOrNone: // char* or NULL if py_return == - // Py_None - { - const char format[3] = "z"; - success = PyArg_Parse(py_return.get(), format, (char **)ret_value); - break; - } - case eScriptReturnTypeBool: { - const char format[2] = "b"; - success = PyArg_Parse(py_return.get(), format, (bool *)ret_value); - break; - } - case eScriptReturnTypeShortInt: { - const char format[2] = "h"; - success = PyArg_Parse(py_return.get(), format, (short *)ret_value); - break; - } - case eScriptReturnTypeShortIntUnsigned: { - const char format[2] = "H"; - success = - PyArg_Parse(py_return.get(), format, (unsigned short *)ret_value); - break; - } - case eScriptReturnTypeInt: { - const char format[2] = "i"; - success = PyArg_Parse(py_return.get(), format, (int *)ret_value); - break; - } - case eScriptReturnTypeIntUnsigned: { - const char format[2] = "I"; - success = PyArg_Parse(py_return.get(), format, (unsigned int *)ret_value); - break; - } - case eScriptReturnTypeLongInt: { - const char format[2] = "l"; - success = PyArg_Parse(py_return.get(), format, (long *)ret_value); - break; - } - case eScriptReturnTypeLongIntUnsigned: { - const char format[2] = "k"; - success = - PyArg_Parse(py_return.get(), format, (unsigned long *)ret_value); - break; - } - case eScriptReturnTypeLongLong: { - const char format[2] = "L"; - success = PyArg_Parse(py_return.get(), format, (long long *)ret_value); - break; - } - case eScriptReturnTypeLongLongUnsigned: { - const char format[2] = "K"; - success = - PyArg_Parse(py_return.get(), format, (unsigned long long *)ret_value); - break; - } - case eScriptReturnTypeFloat: { - const char format[2] = "f"; - success = PyArg_Parse(py_return.get(), format, (float *)ret_value); - break; - } - case eScriptReturnTypeDouble: { - const char format[2] = "d"; - success = PyArg_Parse(py_return.get(), format, (double *)ret_value); - break; - } - case eScriptReturnTypeChar: { - const char format[2] = "c"; - success = PyArg_Parse(py_return.get(), format, (char *)ret_value); - break; - } - case eScriptReturnTypeOpaqueObject: { - success = true; - PyObject *saved_value = py_return.get(); - Py_XINCREF(saved_value); - *((PyObject **)ret_value) = saved_value; - break; - } - } + PythonObject py_return = std::move(maybe_py_return.get()); + assert(py_return.IsValid()); - ret_success = success; + switch (return_type) { + case eScriptReturnTypeCharPtr: // "char *" + { + const char format[3] = "s#"; + return PyArg_Parse(py_return.get(), format, (char **)ret_value); + } + case eScriptReturnTypeCharStrOrNone: // char* or NULL if py_return == + // Py_None + { + const char format[3] = "z"; + return PyArg_Parse(py_return.get(), format, (char **)ret_value); + } + case eScriptReturnTypeBool: { + const char format[2] = "b"; + return PyArg_Parse(py_return.get(), format, (bool *)ret_value); + } + case eScriptReturnTypeShortInt: { + const char format[2] = "h"; + return PyArg_Parse(py_return.get(), format, (short *)ret_value); + } + case eScriptReturnTypeShortIntUnsigned: { + const char format[2] = "H"; + return PyArg_Parse(py_return.get(), format, (unsigned short *)ret_value); + } + case eScriptReturnTypeInt: { + const char format[2] = "i"; + return PyArg_Parse(py_return.get(), format, (int *)ret_value); + } + case eScriptReturnTypeIntUnsigned: { + const char format[2] = "I"; + return PyArg_Parse(py_return.get(), format, (unsigned int *)ret_value); + } + case eScriptReturnTypeLongInt: { + const char format[2] = "l"; + return PyArg_Parse(py_return.get(), format, (long *)ret_value); + } + case eScriptReturnTypeLongIntUnsigned: { + const char format[2] = "k"; + return PyArg_Parse(py_return.get(), format, (unsigned long *)ret_value); + } + case eScriptReturnTypeLongLong: { + const char format[2] = "L"; + return PyArg_Parse(py_return.get(), format, (long long *)ret_value); + } + case eScriptReturnTypeLongLongUnsigned: { + const char format[2] = "K"; + return PyArg_Parse(py_return.get(), format, + (unsigned long long *)ret_value); + } + case eScriptReturnTypeFloat: { + const char format[2] = "f"; + return PyArg_Parse(py_return.get(), format, (float *)ret_value); + } + case eScriptReturnTypeDouble: { + const char format[2] = "d"; + return PyArg_Parse(py_return.get(), format, (double *)ret_value); + } + case eScriptReturnTypeChar: { + const char format[2] = "c"; + return PyArg_Parse(py_return.get(), format, (char *)ret_value); + } + case eScriptReturnTypeOpaqueObject: { + *((PyObject **)ret_value) = py_return.release(); + return true; } - - py_error.Reset(PyRefType::Borrowed, PyErr_Occurred()); - if (py_error.IsValid()) { - ret_success = false; - if (options.GetMaskoutErrors()) { - if (PyErr_GivenExceptionMatches(py_error.get(), PyExc_SyntaxError)) - PyErr_Print(); - PyErr_Clear(); - } } - - return ret_success; } Status ScriptInterpreterPythonImpl::ExecuteMultipleLines( const char *in_string, const ExecuteScriptOptions &options) { - Status error; + + if (in_string == nullptr) + return Status(); Locker locker(this, Locker::AcquireLock | Locker::InitSession | @@ -1192,51 +1153,34 @@ Locker::NoSTDIN, Locker::FreeAcquiredLock | Locker::TearDownSession); - PythonObject return_value; - PythonObject &main_module = GetMainModule(); - PythonDictionary globals(PyRefType::Borrowed, - PyModule_GetDict(main_module.get())); - PythonObject py_error; + PythonModule &main_module = GetMainModule(); + PythonDictionary globals = main_module.GetDictionary(); PythonDictionary locals = GetSessionDictionary(); - if (!locals.IsValid()) locals = unwrapIgnoringErrors( As(globals.GetAttribute(m_dictionary_name))); - if (!locals.IsValid()) locals = globals; - py_error.Reset(PyRefType::Borrowed, PyErr_Occurred()); - if (py_error.IsValid()) - PyErr_Clear(); - - if (in_string != nullptr) { - PythonObject code_object; - code_object.Reset(PyRefType::Owned, - Py_CompileString(in_string, "temp.py", Py_file_input)); - - if (code_object.IsValid()) { -// In Python 2.x, PyEval_EvalCode takes a PyCodeObject, but in Python 3.x, it -// takes a PyObject. They are convertible (hence the function -// PyCode_Check(PyObject*), so we have to do the cast for Python 2.x -#if PY_MAJOR_VERSION >= 3 - PyObject *py_code_obj = code_object.get(); -#else - PyCodeObject *py_code_obj = - reinterpret_cast(code_object.get()); -#endif - return_value.Reset( - PyRefType::Owned, - PyEval_EvalCode(py_code_obj, globals.get(), locals.get())); - } + Expected return_value = + runStringMultiLine(in_string, globals, locals); + if (!return_value) { + Status error; + llvm::handleAllErrors( + return_value.takeError(), + [&](PythonException &E) { + error.SetErrorString(E.ReadBacktrace()); + if (!options.GetMaskoutErrors()) + E.Restore(); + }, + [&](const llvm::ErrorInfoBase &E) { + error.SetErrorString(E.message()); + }); + return error; } - PythonExceptionState exception_state(!options.GetMaskoutErrors()); - if (exception_state.IsError()) - error.SetErrorString(exception_state.Format().c_str()); - - return error; + return Status(); } void ScriptInterpreterPythonImpl::CollectDataForBreakpointCommandCallback( @@ -2034,9 +1978,13 @@ Locker py_lock(this, Locker::AcquireLock | Locker::InitSession | Locker::NoSTDIN); TargetSP target_sp(target->shared_from_this()); - reply_pyobj.Reset(PyRefType::Owned, - (PyObject *)LLDBSWIGPython_GetDynamicSetting( - generic->GetValue(), setting_name, target_sp)); + + auto setting = (PyObject *)LLDBSWIGPython_GetDynamicSetting( + generic->GetValue(), setting_name, target_sp); + if (setting) + reply_pyobj = Take(setting); + else + reply_pyobj.Reset(); PythonDictionary py_dict(PyRefType::Borrowed, reply_pyobj.get()); return py_dict.CreateStructuredDictionary(); diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h @@ -363,7 +363,7 @@ eIOHandlerWatchpoint }; - PythonObject &GetMainModule(); + PythonModule &GetMainModule(); PythonDictionary &GetSessionDictionary(); @@ -377,7 +377,7 @@ PythonObject m_saved_stdin; PythonObject m_saved_stdout; PythonObject m_saved_stderr; - PythonObject m_main_module; + PythonModule m_main_module; PythonDictionary m_session_dict; PythonDictionary m_sys_module_dict; PythonObject m_run_one_line_function; diff --git a/lldb/unittests/ScriptInterpreter/Python/CMakeLists.txt b/lldb/unittests/ScriptInterpreter/Python/CMakeLists.txt --- a/lldb/unittests/ScriptInterpreter/Python/CMakeLists.txt +++ b/lldb/unittests/ScriptInterpreter/Python/CMakeLists.txt @@ -1,6 +1,5 @@ add_lldb_unittest(ScriptInterpreterPythonTests PythonDataObjectsTests.cpp - PythonExceptionStateTests.cpp PythonTestSuite.cpp LINK_LIBS diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonDataObjectsTests.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonDataObjectsTests.cpp --- a/lldb/unittests/ScriptInterpreter/Python/PythonDataObjectsTests.cpp +++ b/lldb/unittests/ScriptInterpreter/Python/PythonDataObjectsTests.cpp @@ -21,6 +21,7 @@ using namespace lldb_private; using namespace lldb_private::python; +using llvm::Expected; class PythonDataObjectsTest : public PythonTestSuite { public: @@ -771,4 +772,32 @@ } #endif +} + +TEST_F(PythonDataObjectsTest, TestExceptions) { + + static const char script[] = R"( +def foo(): + return bar() +def bar(): + return baz() +def baz(): + return 1 / 0 +)"; + + PythonScript foo(script, "foo"); + Expected r = foo(); + + bool failed = !r; + ASSERT_TRUE(failed); + + std::string backtrace; + llvm::handleAllErrors(r.takeError(), [&](const PythonException &E) { + backtrace = E.ReadBacktrace(); + }); + + EXPECT_NE(backtrace.find("line 3, in foo"), std::string::npos); + EXPECT_NE(backtrace.find("line 5, in bar"), std::string::npos); + EXPECT_NE(backtrace.find("line 7, in baz"), std::string::npos); + EXPECT_NE(backtrace.find("ZeroDivisionError"), std::string::npos); } \ No newline at end of file diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonExceptionStateTests.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonExceptionStateTests.cpp deleted file mode 100644 --- a/lldb/unittests/ScriptInterpreter/Python/PythonExceptionStateTests.cpp +++ /dev/null @@ -1,164 +0,0 @@ -//===-- PythonExceptionStateTest.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 -// -//===----------------------------------------------------------------------===// - -#include "gtest/gtest.h" - -#include "Plugins/ScriptInterpreter/Python/PythonDataObjects.h" -#include "Plugins/ScriptInterpreter/Python/PythonExceptionState.h" -#include "Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h" -#include "Plugins/ScriptInterpreter/Python/lldb-python.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, TestResetSemantics) { - PyErr_Clear(); - - // Resetting when auto-restore is true should restore. - RaiseException(); - PythonExceptionState error(true); - EXPECT_TRUE(error.IsError()); - EXPECT_FALSE(PythonExceptionState::HasErrorOccurred()); - error.Reset(); - EXPECT_FALSE(error.IsError()); - EXPECT_TRUE(PythonExceptionState::HasErrorOccurred()); - - PyErr_Clear(); - - // Resetting when auto-restore is false should discard. - RaiseException(); - PythonExceptionState error2(false); - EXPECT_TRUE(error2.IsError()); - EXPECT_FALSE(PythonExceptionState::HasErrorOccurred()); - error2.Reset(); - EXPECT_FALSE(error2.IsError()); - EXPECT_FALSE(PythonExceptionState::HasErrorOccurred()); - - PyErr_Clear(); -} - -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(); -} - -TEST_F(PythonExceptionStateTest, TestAutoRestoreChanged) { - // Test that if we re-acquire with different auto-restore semantics, - // that the new semantics are respected. - PyErr_Clear(); - - RaiseException(); - PythonExceptionState error(false); - EXPECT_TRUE(error.IsError()); - - error.Reset(); - EXPECT_FALSE(error.IsError()); - EXPECT_FALSE(PythonExceptionState::HasErrorOccurred()); - - RaiseException(); - error.Acquire(true); - EXPECT_TRUE(error.IsError()); - EXPECT_FALSE(PythonExceptionState::HasErrorOccurred()); - - error.Reset(); - EXPECT_FALSE(error.IsError()); - EXPECT_TRUE(PythonExceptionState::HasErrorOccurred()); - - PyErr_Clear(); -}