| //===-- ScriptedPythonInterface.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_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDPYTHONINTERFACE_H | |
| #define LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDPYTHONINTERFACE_H | |
| #include <optional> | |
| #include <sstream> | |
| #include <tuple> | |
| #include <type_traits> | |
| #include <utility> | |
| #include "lldb/API/SBCommandReturnObject.h" | |
| #include "lldb/Interpreter/Interfaces/ScriptedInterface.h" | |
| #include "lldb/Utility/DataBufferHeap.h" | |
| #include "lldb/Utility/Policy.h" | |
| #include "../PythonDataObjects.h" | |
| #include "../SWIGPythonBridge.h" | |
| #include "../ScriptInterpreterPythonImpl.h" | |
| namespace lldb_private { | |
| class ScriptInterpreterPythonImpl; | |
| class ScriptedPythonInterface : virtual public ScriptedInterface { | |
| public: | |
| ScriptedPythonInterface(ScriptInterpreterPythonImpl &interpreter); | |
| ~ScriptedPythonInterface() override = default; | |
| enum class AbstractMethodCheckerCases { | |
| eNotImplemented, | |
| eNotAllocated, | |
| eNotCallable, | |
| eUnknownArgumentCount, | |
| eInvalidArgumentCount, | |
| eValid | |
| }; | |
| struct AbstractMethodCheckerPayload { | |
| struct InvalidArgumentCountPayload { | |
| InvalidArgumentCountPayload(size_t required, size_t actual) | |
| : required_argument_count(required), actual_argument_count(actual) {} | |
| size_t required_argument_count; | |
| size_t actual_argument_count; | |
| }; | |
| AbstractMethodCheckerCases checker_case; | |
| std::variant<std::monostate, InvalidArgumentCountPayload, std::string> | |
| payload; | |
| }; | |
| llvm::Expected<FileSpec> GetScriptedModulePath() override { | |
| using namespace python; | |
| using Locker = ScriptInterpreterPythonImpl::Locker; | |
| Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN, | |
| Locker::FreeLock); | |
| if (!m_object_instance_sp) | |
| return llvm::createStringError("scripted Interface has invalid object"); | |
| PythonObject py_obj = | |
| PythonObject(PyRefType::Borrowed, | |
| static_cast<PyObject *>(m_object_instance_sp->GetValue())); | |
| if (!py_obj.IsAllocated()) | |
| return llvm::createStringError( | |
| "scripted Interface has invalid python object"); | |
| PythonObject py_obj_class = py_obj.GetAttributeValue("__class__"); | |
| if (!py_obj_class.IsValid()) | |
| return llvm::createStringError( | |
| "scripted Interface python object is missing '__class__' attribute"); | |
| PythonObject py_obj_module = py_obj_class.GetAttributeValue("__module__"); | |
| if (!py_obj_module.IsValid()) | |
| return llvm::createStringError( | |
| "scripted Interface python object '__class__' is missing " | |
| "'__module__' attribute"); | |
| PythonString py_obj_module_str = py_obj_module.Str(); | |
| if (!py_obj_module_str.IsValid()) | |
| return llvm::createStringError( | |
| "scripted Interface python object '__class__.__module__' attribute " | |
| "is not a string"); | |
| llvm::StringRef py_obj_module_str_ref = py_obj_module_str.GetString(); | |
| PythonModule py_module = PythonModule::AddModule(py_obj_module_str_ref); | |
| if (!py_module.IsValid()) | |
| return llvm::createStringError("failed to import '%s' module", | |
| py_obj_module_str_ref.data()); | |
| PythonObject py_module_file = py_module.GetAttributeValue("__file__"); | |
| if (!py_module_file.IsValid()) | |
| return llvm::createStringError( | |
| "module '%s' is missing '__file__' attribute", | |
| py_obj_module_str_ref.data()); | |
| PythonString py_module_file_str = py_module_file.Str(); | |
| if (!py_module_file_str.IsValid()) | |
| return llvm::createStringError( | |
| "module '%s.__file__' attribute is not a string", | |
| py_obj_module_str_ref.data()); | |
| return FileSpec(py_module_file_str.GetString()); | |
| } | |
| llvm::Expected<std::map<llvm::StringLiteral, AbstractMethodCheckerPayload>> | |
| CheckAbstractMethodImplementation( | |
| const python::PythonObject &obj_class) const { | |
| using namespace python; | |
| std::map<llvm::StringLiteral, AbstractMethodCheckerPayload> checker; | |
| #define SET_CASE_AND_CONTINUE(method_name, case) \ | |
| { \ | |
| checker[method_name] = {case, {}}; \ | |
| continue; \ | |
| } | |
| for (const AbstractMethodRequirement &requirement : | |
| GetAbstractMethodRequirements()) { | |
| llvm::StringLiteral method_name = requirement.name; | |
| // Look up via attribute access so inherited methods are found; the | |
| // class's own __dict__ omits anything defined on a base class. | |
| if (!obj_class.HasAttribute(method_name)) | |
| SET_CASE_AND_CONTINUE(method_name, | |
| AbstractMethodCheckerCases::eNotImplemented) | |
| PythonObject attr = obj_class.GetAttributeValue(method_name); | |
| if (!attr.IsAllocated()) | |
| SET_CASE_AND_CONTINUE(method_name, | |
| AbstractMethodCheckerCases::eNotAllocated) | |
| PythonCallable callable = attr.AsType<PythonCallable>(); | |
| if (!callable) | |
| SET_CASE_AND_CONTINUE(method_name, | |
| AbstractMethodCheckerCases::eNotCallable) | |
| if (!requirement.min_arg_count) | |
| SET_CASE_AND_CONTINUE(method_name, AbstractMethodCheckerCases::eValid) | |
| auto arg_info_or_err = callable.GetArgInfo(); | |
| if (!arg_info_or_err) { | |
| checker[method_name] = { | |
| AbstractMethodCheckerCases::eUnknownArgumentCount, | |
| ExtractPythonError(arg_info_or_err.takeError())}; | |
| continue; | |
| } | |
| PythonCallable::ArgInfo arg_info = *arg_info_or_err; | |
| if (requirement.min_arg_count <= arg_info.max_positional_args) { | |
| SET_CASE_AND_CONTINUE(method_name, AbstractMethodCheckerCases::eValid) | |
| } else { | |
| checker[method_name] = { | |
| AbstractMethodCheckerCases::eInvalidArgumentCount, | |
| AbstractMethodCheckerPayload::InvalidArgumentCountPayload( | |
| requirement.min_arg_count, arg_info.max_positional_args)}; | |
| } | |
| } | |
| #undef SET_CASE_AND_CONTINUE | |
| return checker; | |
| } | |
| /// Diagnose every abstract-method violation on \a obj_class at once. | |
| /// | |
| /// \a obj_class is a class object, whether resolved by name or taken from an | |
| /// instance's `__class__`. Resolving it by name means this can run before any | |
| /// instance exists (see CreatePluginObject), which uses it to reject a | |
| /// malformed class without executing its `__init__`. | |
| llvm::Error CheckAbstractMethods(const python::PythonObject &obj_class, | |
| llvm::StringRef qualified_class_name) const { | |
| Log *log = GetLog(LLDBLog::Script); | |
| // Per-method diagnostics name the class the way Python does, unqualified. | |
| python::PythonString obj_class_name = | |
| obj_class.GetAttributeValue("__name__").AsType<python::PythonString>(); | |
| llvm::StringRef class_name = obj_class_name.IsValid() | |
| ? obj_class_name.GetString() | |
| : qualified_class_name; | |
| auto create_error = [](llvm::StringLiteral format, auto &&...ts) { | |
| return llvm::createStringError( | |
| llvm::formatv(format.data(), std::forward<decltype(ts)>(ts)...) | |
| .str()); | |
| }; | |
| auto checker_or_err = CheckAbstractMethodImplementation(obj_class); | |
| if (!checker_or_err) | |
| return checker_or_err.takeError(); | |
| llvm::Error abstract_method_errors = llvm::Error::success(); | |
| for (const auto &method_checker : *checker_or_err) | |
| switch (method_checker.second.checker_case) { | |
| case AbstractMethodCheckerCases::eNotImplemented: | |
| abstract_method_errors = llvm::joinErrors( | |
| std::move(abstract_method_errors), | |
| create_error("abstract method {0}.{1} not implemented", class_name, | |
| method_checker.first)); | |
| break; | |
| case AbstractMethodCheckerCases::eNotAllocated: | |
| abstract_method_errors = llvm::joinErrors( | |
| std::move(abstract_method_errors), | |
| create_error("abstract method {0}.{1} not allocated", class_name, | |
| method_checker.first)); | |
| break; | |
| case AbstractMethodCheckerCases::eNotCallable: | |
| abstract_method_errors = llvm::joinErrors( | |
| std::move(abstract_method_errors), | |
| create_error("abstract method {0}.{1} not callable", class_name, | |
| method_checker.first)); | |
| break; | |
| case AbstractMethodCheckerCases::eUnknownArgumentCount: { | |
| const std::string *py_error = | |
| std::get_if<std::string>(&method_checker.second.payload); | |
| abstract_method_errors = llvm::joinErrors( | |
| std::move(abstract_method_errors), | |
| create_error( | |
| "abstract method {0}.{1} has unknown argument count: {2}", | |
| class_name, method_checker.first, | |
| py_error ? *py_error : "<no further information>")); | |
| } break; | |
| case AbstractMethodCheckerCases::eInvalidArgumentCount: { | |
| auto &payload_variant = method_checker.second.payload; | |
| if (!std::holds_alternative< | |
| AbstractMethodCheckerPayload::InvalidArgumentCountPayload>( | |
| payload_variant)) { | |
| abstract_method_errors = llvm::joinErrors( | |
| std::move(abstract_method_errors), | |
| create_error( | |
| "abstract method {0}.{1} has unexpected argument count", | |
| class_name, method_checker.first)); | |
| } else { | |
| auto payload = std::get< | |
| AbstractMethodCheckerPayload::InvalidArgumentCountPayload>( | |
| payload_variant); | |
| abstract_method_errors = llvm::joinErrors( | |
| std::move(abstract_method_errors), | |
| create_error("abstract method {0}.{1} has unexpected " | |
| "argument count (expected {2} but has {3})", | |
| class_name, method_checker.first, | |
| payload.required_argument_count, | |
| payload.actual_argument_count)); | |
| } | |
| } break; | |
| case AbstractMethodCheckerCases::eValid: | |
| LLDB_LOG(log, "Abstract method {0}.{1} implemented & valid.", | |
| class_name, method_checker.first); | |
| break; | |
| } | |
| if (abstract_method_errors) { | |
| Status error = Status::FromError(std::move(abstract_method_errors)); | |
| LLDB_LOG(log, "Abstract method error in {0}:\n{1}", qualified_class_name, | |
| error.AsCString()); | |
| return error.ToError(); | |
| } | |
| return llvm::Error::success(); | |
| } | |
| template <typename... Args> | |
| llvm::Expected<StructuredData::GenericSP> | |
| CreatePluginObject(const ScriptedMetadata &scripted_metadata, | |
| StructuredData::Generic *script_obj, Args... args) { | |
| using namespace python; | |
| using Locker = ScriptInterpreterPythonImpl::Locker; | |
| auto create_error = [](llvm::StringLiteral format, auto &&...ts) { | |
| return llvm::createStringError( | |
| llvm::formatv(format.data(), std::forward<decltype(ts)>(ts)...) | |
| .str()); | |
| }; | |
| m_scripted_metadata = scripted_metadata; | |
| llvm::StringRef class_name = scripted_metadata.GetClassName(); | |
| bool has_class_name = !class_name.empty(); | |
| bool has_interpreter_dict = | |
| !(llvm::StringRef(m_interpreter.GetDictionaryName()).empty()); | |
| if (!has_class_name && !has_interpreter_dict && !script_obj) { | |
| if (!has_class_name) | |
| return create_error("missing script class name"); | |
| else if (!has_interpreter_dict) | |
| return create_error("invalid script interpreter dictionary"); | |
| else | |
| return create_error("missing scripting object"); | |
| } | |
| std::optional<PolicyStack::Guard> policy_guard; | |
| if (!UserCanRunDirectly()) | |
| policy_guard = PolicyStack::Get().PushScriptedExtensionCall(); | |
| Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN, | |
| Locker::FreeLock); | |
| PythonObject result = {}; | |
| if (script_obj) { | |
| result = PythonObject(PyRefType::Borrowed, | |
| static_cast<PyObject *>(script_obj->GetValue())); | |
| } else { | |
| auto dict = | |
| PythonModule::MainModule().ResolveName<python::PythonDictionary>( | |
| m_interpreter.GetDictionaryName()); | |
| if (!dict.IsAllocated()) | |
| return create_error("could not find interpreter dictionary: {0}", | |
| m_interpreter.GetDictionaryName()); | |
| auto init = | |
| PythonObject::ResolveNameWithDictionary<python::PythonCallable>( | |
| class_name, dict); | |
| if (!init.IsAllocated()) | |
| return create_error("could not find script class: {0}", | |
| class_name.data()); | |
| std::tuple<Args...> original_args = std::forward_as_tuple(args...); | |
| auto transformed_args = TransformArgs(original_args); | |
| std::string error_string; | |
| llvm::Expected<PythonCallable::ArgInfo> arg_info = init.GetArgInfo(); | |
| if (!arg_info) { | |
| llvm::handleAllErrors( | |
| arg_info.takeError(), | |
| [&](PythonException &E) { error_string.append(E.ReadBacktrace()); }, | |
| [&](const llvm::ErrorInfoBase &E) { | |
| error_string.append(E.message()); | |
| }); | |
| return llvm::createStringError(llvm::inconvertibleErrorCode(), | |
| error_string); | |
| } | |
| if (llvm::Error error = CheckAbstractMethods(init, class_name)) | |
| return std::move(error); | |
| llvm::Expected<PythonObject> expected_return_object = | |
| create_error("resulting object is not initialized"); | |
| // This relax the requirement on the number of argument for | |
| // initializing scripting extension if the size of the interface | |
| // parameter pack contains 1 less element than the extension maximum | |
| // number of positional arguments for this initializer. | |
| // | |
| // This addresses the cases where the embedded interpreter session | |
| // dictionary is passed to the extension initializer which is not used | |
| // most of the time. | |
| // Note, though none of our API's suggest defining the interfaces with | |
| // varargs, we have some extant clients that were doing that. To keep | |
| // from breaking them, we just say putting a varargs in these signatures | |
| // turns off argument checking. | |
| size_t num_args = sizeof...(Args); | |
| if (arg_info->max_positional_args != PythonCallable::ArgInfo::UNBOUNDED && | |
| num_args != arg_info->max_positional_args) { | |
| if (num_args != arg_info->max_positional_args - 1) { | |
| // `expected_return_object` starts in an error state; consume it | |
| // before we return with a different error, or its destructor | |
| // will abort. | |
| llvm::consumeError(expected_return_object.takeError()); | |
| return create_error("passed arguments ({0}) doesn't match the number " | |
| "of expected arguments ({1})", | |
| num_args, arg_info->max_positional_args); | |
| } | |
| std::apply( | |
| [&init, &expected_return_object](auto &&...args) { | |
| if (!expected_return_object) | |
| llvm::consumeError(expected_return_object.takeError()); | |
| expected_return_object = init.Call(args...); | |
| }, | |
| std::tuple_cat(transformed_args, std::make_tuple(dict))); | |
| } else { | |
| std::apply( | |
| [&init, &expected_return_object](auto &&...args) { | |
| if (!expected_return_object) | |
| llvm::consumeError(expected_return_object.takeError()); | |
| expected_return_object = init.Call(args...); | |
| }, | |
| transformed_args); | |
| } | |
| if (!expected_return_object) | |
| // Drain the Python exception into a plain string while the GIL is | |
| // still held: `PythonException` owns raw `PyObject*` references, and | |
| // `py_lock` (and the GIL it holds) is released as this function | |
| // returns, before the caller gets a chance to touch the error. | |
| return llvm::createStringError( | |
| ExtractPythonError(expected_return_object.takeError())); | |
| result = expected_return_object.get(); | |
| } | |
| if (!result.IsValid()) | |
| return create_error("resulting object is not a valid Python Object"); | |
| if (!result.HasAttribute("__class__")) | |
| return create_error("resulting object doesn't have '__class__' member"); | |
| PythonObject obj_class = result.GetAttributeValue("__class__"); | |
| if (!obj_class.IsValid()) | |
| return create_error("resulting class object is not a valid"); | |
| if (!obj_class.HasAttribute("__name__")) | |
| return create_error( | |
| "resulting object class doesn't have '__name__' member"); | |
| PythonString obj_class_name = | |
| obj_class.GetAttributeValue("__name__").AsType<PythonString>(); | |
| // We were handed an instance rather than building one, so there was no | |
| // constructor to run the check ahead of; validate it now. | |
| if (script_obj) | |
| if (llvm::Error error = | |
| CheckAbstractMethods(obj_class, obj_class_name.GetString())) | |
| return std::move(error); | |
| m_object_instance_sp = StructuredData::GenericSP( | |
| new StructuredPythonObject(std::move(result))); | |
| return m_object_instance_sp; | |
| } | |
| /// Call a static method on a Python class without creating an instance. | |
| /// | |
| /// This method resolves a Python class by name and calls a static method | |
| /// on it, returning the result. This is useful for calling class-level | |
| /// methods that don't require an instance. | |
| /// | |
| /// \param class_name The fully-qualified name of the Python class. | |
| /// \param method_name The name of the static method to call. | |
| /// \param args Arguments to pass to the static method. | |
| /// | |
| /// \return The return value of the static method call, or an error. | |
| template <typename T = StructuredData::ObjectSP, typename... Args> | |
| llvm::Expected<T> CallStaticMethod(llvm::StringRef class_name, | |
| llvm::StringRef method_name, | |
| Args &&...args) { | |
| using namespace python; | |
| using Locker = ScriptInterpreterPythonImpl::Locker; | |
| std::string caller_signature = | |
| llvm::Twine(LLVM_PRETTY_FUNCTION + llvm::Twine(" (") + | |
| llvm::Twine(class_name) + llvm::Twine(".") + | |
| llvm::Twine(method_name) + llvm::Twine(")")) | |
| .str(); | |
| if (class_name.empty()) | |
| return LogAndError(caller_signature, "missing script class name"); | |
| std::optional<PolicyStack::Guard> policy_guard; | |
| if (!UserCanRunDirectly()) | |
| policy_guard = PolicyStack::Get().PushScriptedExtensionCall(); | |
| Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN, | |
| Locker::FreeLock); | |
| // Get the interpreter dictionary. | |
| auto dict = | |
| PythonModule::MainModule().ResolveName<python::PythonDictionary>( | |
| m_interpreter.GetDictionaryName()); | |
| if (!dict.IsAllocated()) | |
| return LogAndError(caller_signature, | |
| "could not find interpreter dictionary: {0}", | |
| m_interpreter.GetDictionaryName()); | |
| // Resolve the class. | |
| auto class_obj = | |
| PythonObject::ResolveNameWithDictionary<python::PythonCallable>( | |
| class_name, dict); | |
| if (!class_obj.IsAllocated()) | |
| return LogAndError(caller_signature, "could not find script class: {0}", | |
| class_name); | |
| // Get the static method from the class. | |
| if (!class_obj.HasAttribute(method_name)) | |
| return llvm::make_error<UnimplementedError>( | |
| llvm::formatv("{0}.{1}", class_name, method_name).str()); | |
| PythonCallable method = | |
| class_obj.GetAttributeValue(method_name).AsType<PythonCallable>(); | |
| if (!method.IsAllocated()) | |
| return LogAndError(caller_signature, "method {0}.{1} is not callable", | |
| class_name, method_name); | |
| // Transform the arguments. | |
| std::tuple<Args...> original_args = std::forward_as_tuple(args...); | |
| auto transformed_args = TransformArgs(original_args); | |
| // Call the static method. | |
| llvm::Expected<PythonObject> expected_return_object = | |
| llvm::createStringError("not initialized"); | |
| std::apply( | |
| [&method, &expected_return_object](auto &&...args) { | |
| if (!expected_return_object) | |
| llvm::consumeError(expected_return_object.takeError()); | |
| expected_return_object = method.Call(args...); | |
| }, | |
| transformed_args); | |
| if (llvm::Error e = expected_return_object.takeError()) { | |
| // TODO: Stringify `args` and include them in the message so users | |
| // can see what was passed to the failing call (e.g. | |
| // `read_memory_at_address(0x500000000, 4)`). Requires a SFINAE | |
| // helper that falls back to a placeholder for types without a | |
| // format_provider / operator<<. | |
| return LogAndError( | |
| caller_signature, "python exception in {0} method '{1}': {2}", | |
| class_name, method_name, ExtractPythonError(std::move(e))); | |
| } | |
| PythonObject py_return = std::move(expected_return_object.get()); | |
| // Re-assign reference and pointer arguments if needed. | |
| if (sizeof...(Args) > 0) | |
| if (!ReassignPtrsOrRefsArgs(original_args, transformed_args)) | |
| return LogAndError( | |
| caller_signature, | |
| "couldn't re-assign reference and pointer arguments"); | |
| // Extract value from Python object (handles unallocated case). | |
| if (!py_return.IsAllocated()) | |
| return T{}; | |
| return ExtractValueFromPythonObject<T>(py_return); | |
| } | |
| protected: | |
| /// Extract detailed error message including Python backtrace if available. | |
| /// | |
| /// This helper processes llvm::Error objects that may contain PythonException | |
| /// instances, extracting full Python backtraces when available. | |
| /// | |
| /// \param error The llvm::Error to extract information from. | |
| /// \return A string containing the error message, including full Python | |
| /// backtrace if the error was a PythonException. | |
| static std::string ExtractPythonError(llvm::Error error) { | |
| std::string error_msg; | |
| llvm::handleAllErrors( | |
| std::move(error), | |
| [&](python::PythonException &E) { error_msg = E.ReadBacktrace(); }, | |
| [&](const llvm::ErrorInfoBase &E) { error_msg = E.message(); }); | |
| return error_msg; | |
| } | |
| /// Log \a message against \a caller_name and return it as an error. | |
| /// | |
| /// The `Expected`-returning counterpart of | |
| /// `ScriptedInterface::ErrorWithMessage`: it reports the failure instead of | |
| /// folding it into a default-constructed value. | |
| template <typename... Ts> | |
| static llvm::Error LogAndError(llvm::StringRef caller_name, | |
| const char *format, Ts &&...ts) { | |
| std::string message = llvm::formatv(format, std::forward<Ts>(ts)...).str(); | |
| LLDB_LOGF(GetLog(LLDBLog::Script), "%s ERROR = %s", caller_name.data(), | |
| message.c_str()); | |
| return llvm::createStringError(message); | |
| } | |
| /// Log the failure in \a value_or_err and fall back to a default-constructed | |
| /// \c T. | |
| /// | |
| /// For entry points whose return type cannot express failure. Prefer | |
| /// propagating the error wherever the signature can carry it; this is the | |
| /// stop-gap, and it is at least strictly better than dropping the error. | |
| /// | |
| /// Logs any failure, an unimplemented method included: a site that dispatches | |
| /// with Dispatch() rather than DispatchToOptional() is asserting the method | |
| /// should have been there. | |
| template <typename T> | |
| static T LogAndDefault(llvm::Expected<T> value_or_err, | |
| llvm::StringRef caller) { | |
| if (value_or_err) | |
| return std::move(*value_or_err); | |
| // Consume the error before logging: LLDB_LOGF doesn't evaluate its | |
| // arguments when the channel is disabled, which would leave the error | |
| // unchecked and abort. | |
| std::string message = llvm::toString(value_or_err.takeError()); | |
| LLDB_LOGF(GetLog(LLDBLog::Script), "%s failed: %s", caller.str().c_str(), | |
| message.c_str()); | |
| return T{}; | |
| } | |
| /// Call an optional \a method_name, reporting "the script doesn't implement | |
| /// it" as \c std::nullopt rather than as a failure. | |
| /// | |
| /// Use this for callbacks a script may legitimately leave out. Any other | |
| /// failure - in particular an exception raised inside the method - | |
| /// propagates; this never papers over a method that ran and failed. | |
| template <typename T = StructuredData::ObjectSP, typename... Args> | |
| llvm::Expected<std::optional<T>> | |
| DispatchToOptional(llvm::StringRef method_name, Args &&...args) { | |
| if (!llvm::is_contained(GetOptionalMethods(), method_name)) | |
| return LogAndError( | |
| LLVM_PRETTY_FUNCTION, | |
| "method '{0}' is not declared optional: list it in " | |
| "GetOptionalMethods(), or dispatch it with Dispatch(), " | |
| "where a missing method is an error", | |
| method_name); | |
| llvm::Expected<T> value_or_err = | |
| Dispatch<T>(method_name, std::forward<Args>(args)...); | |
| if (value_or_err) | |
| return std::move(*value_or_err); | |
| if (value_or_err.template errorIsA<UnimplementedError>()) { | |
| llvm::consumeError(value_or_err.takeError()); | |
| return std::nullopt; | |
| } | |
| return value_or_err.takeError(); | |
| } | |
| template <typename T = StructuredData::ObjectSP> | |
| llvm::Expected<T> ExtractValueFromPythonObject(python::PythonObject &p) { | |
| return p.CreateStructuredObject(); | |
| } | |
| /// Call \a method_name on the scripted object. | |
| /// | |
| /// The returned \c Expected separates the two outcomes that a plain return | |
| /// value cannot: a successfully extracted value (which may legitimately be | |
| /// empty or null, e.g. when the Python method returns \c None) and a | |
| /// failure to call the method at all. | |
| /// | |
| /// A class that simply doesn't implement \a method_name fails with an | |
| /// \c UnimplementedError, so callers of optional callbacks can recognize | |
| /// and ignore that case without also swallowing real exceptions. | |
| template <typename T = StructuredData::ObjectSP, typename... Args> | |
| llvm::Expected<T> Dispatch(llvm::StringRef method_name, Args &&...args) { | |
| using namespace python; | |
| using Locker = ScriptInterpreterPythonImpl::Locker; | |
| std::string caller_signature = | |
| llvm::Twine(LLVM_PRETTY_FUNCTION + llvm::Twine(" (") + | |
| llvm::Twine(method_name) + llvm::Twine(")")) | |
| .str(); | |
| if (!m_object_instance_sp) | |
| return LogAndError(caller_signature, "python object ill-formed"); | |
| std::optional<PolicyStack::Guard> policy_guard; | |
| if (!UserCanRunDirectly()) | |
| policy_guard = PolicyStack::Get().PushScriptedExtensionCall(); | |
| Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN, | |
| Locker::FreeLock); | |
| PythonObject implementor(PyRefType::Borrowed, | |
| (PyObject *)m_object_instance_sp->GetValue()); | |
| if (!implementor.IsAllocated()) | |
| return LogAndError(caller_signature, "python implementor not allocated"); | |
| PythonObject py_method = implementor.GetAttributeValue(method_name); | |
| if (!py_method.IsAllocated()) | |
| return llvm::make_error<UnimplementedError>( | |
| llvm::formatv("{0}.{1}", | |
| GetScriptedMetadata() | |
| ? GetScriptedMetadata()->GetClassName() | |
| : "<unknown>", | |
| method_name) | |
| .str()); | |
| std::tuple<Args...> original_args = std::forward_as_tuple(args...); | |
| auto transformed_args = TransformArgs(original_args); | |
| // Trim trailing args if the Python method accepts fewer positional | |
| // parameters than we're passing (e.g. `num_children(self)` vs. | |
| // `num_children(self, max_count)`). | |
| size_t call_arity = sizeof...(Args); | |
| if (PythonCallable callable = py_method.AsType<PythonCallable>(); | |
| callable.IsAllocated()) { | |
| if (llvm::Expected<PythonCallable::ArgInfo> arg_info = | |
| callable.GetArgInfo()) { | |
| if (arg_info->max_positional_args != | |
| PythonCallable::ArgInfo::UNBOUNDED && | |
| arg_info->max_positional_args < call_arity) | |
| call_arity = arg_info->max_positional_args; | |
| } else { | |
| llvm::consumeError(arg_info.takeError()); | |
| } | |
| } | |
| llvm::Expected<PythonObject> expected_return_object = | |
| llvm::createStringError("not initialized"); | |
| CallWithArity(call_arity, transformed_args, | |
| std::make_index_sequence<sizeof...(Args) + 1>{}, | |
| [&implementor, &method_name, | |
| &expected_return_object](auto &&...call_args) { | |
| if (!expected_return_object) | |
| llvm::consumeError(expected_return_object.takeError()); | |
| expected_return_object = implementor.CallMethod( | |
| method_name.data(), call_args...); | |
| }); | |
| if (llvm::Error e = expected_return_object.takeError()) { | |
| // TODO: Stringify `args` and include them in the message so users | |
| // can see what was passed to the failing call (e.g. | |
| // `read_memory_at_address(0x500000000, 4)`). Requires a SFINAE | |
| // helper that falls back to a placeholder for types without a | |
| // format_provider / operator<<. | |
| // | |
| // Drain the Python exception into a plain string while the GIL is | |
| // still held: `PythonException` owns raw `PyObject*` references, and | |
| // `py_lock` is released as this function returns. | |
| return LogAndError( | |
| caller_signature, "python exception in {0} method '{1}': {2}", | |
| GetScriptedMetadata() ? GetScriptedMetadata()->GetClassName() | |
| : "<unknown>", | |
| method_name, ExtractPythonError(std::move(e))); | |
| } | |
| PythonObject py_return = std::move(expected_return_object.get()); | |
| // Now that we called the python method with the transformed arguments, | |
| // we need to iterate again over both the original and transformed | |
| // parameter pack, and transform back the parameter that were passed in | |
| // the original parameter pack as references or pointers. | |
| if (sizeof...(Args) > 0) | |
| if (!ReassignPtrsOrRefsArgs(original_args, transformed_args)) | |
| return LogAndError( | |
| caller_signature, | |
| "couldn't re-assign reference and pointer arguments"); | |
| if (!py_return.IsAllocated()) | |
| return T{}; | |
| return ExtractValueFromPythonObject<T>(py_return); | |
| } | |
| /// Call \a method_name and fold both failure channels into one `Status`: | |
| /// a failure to call the method at all, and the `SBError` the method | |
| /// returned. The latter used to be dropped on the floor. | |
| template <typename... Args> | |
| Status GetStatusFromMethod(llvm::StringRef method_name, Args &&...args) { | |
| llvm::Expected<Status> status_or_err = | |
| Dispatch<Status>(method_name, std::forward<Args>(args)...); | |
| if (!status_or_err) | |
| return Status::FromError(status_or_err.takeError()); | |
| return std::move(*status_or_err); | |
| } | |
| template <typename T> T Transform(T object) { | |
| // No Transformation for generic usage | |
| return {object}; | |
| } | |
| python::PythonObject Transform(bool arg) { | |
| // Boolean arguments need to be turned into python objects. | |
| return python::PythonBoolean(arg); | |
| } | |
| python::PythonObject Transform(const Status &arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg.Clone()); | |
| } | |
| python::PythonObject Transform(Status &&arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(std::move(arg)); | |
| } | |
| python::PythonObject Transform(const StructuredDataImpl &arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| template <typename T, typename = std::enable_if_t< | |
| std::is_base_of_v<StructuredData::Object, T>>> | |
| python::PythonObject Transform(std::shared_ptr<T> arg) { | |
| return Transform(StructuredDataImpl(arg)); | |
| } | |
| python::PythonObject Transform(lldb::ExecutionContextRefSP arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(lldb::TargetSP arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(lldb::BreakpointSP arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(lldb::BreakpointLocationSP arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(lldb::ProcessSP arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(lldb::ThreadSP arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(lldb::StackFrameListSP arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(lldb::ThreadPlanSP arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(lldb::ProcessAttachInfoSP arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(lldb::ProcessLaunchInfoSP arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(Event *arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(const SymbolContext &arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(lldb::StreamSP arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg.get()); | |
| } | |
| python::PythonObject Transform(lldb::StackFrameSP arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(lldb::DataExtractorSP arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(lldb::DescriptionLevel arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(lldb::StepType arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(lldb::ValueObjectSP arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(lldb::DebuggerSP arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| python::PythonObject Transform(const std::vector<std::string> &arg) { | |
| python::PythonList list(python::PyInitialValue::Empty); | |
| for (const std::string &s : arg) | |
| list.AppendItem(python::PythonString(s)); | |
| return list; | |
| } | |
| python::ScopedPythonObject<lldb::SBCommandReturnObject> | |
| Transform(CommandReturnObject *arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(*arg); | |
| } | |
| python::PythonObject Transform(const TypeSummaryOptions &arg) { | |
| return python::SWIGBridge::ToSWIGWrapper(arg); | |
| } | |
| template <typename T, typename U> | |
| void ReverseTransform(T &original_arg, U transformed_arg, Status &error) { | |
| // If U is not a PythonObject, don't touch it! | |
| } | |
| template <typename T> | |
| void ReverseTransform(T &original_arg, python::PythonObject transformed_arg, | |
| Status &error) { | |
| llvm::Expected<T> value_or_err = | |
| ExtractValueFromPythonObject<T>(transformed_arg); | |
| if (!value_or_err) { | |
| error = Status::FromError(value_or_err.takeError()); | |
| return; | |
| } | |
| original_arg = std::move(*value_or_err); | |
| } | |
| // Read-only arguments (passed as `const T&`) have nothing to write back: | |
| // there's no `T` value to reassign into a const reference, and no | |
| // `ExtractValueFromPythonObject<T>` specialization should be required just | |
| // to satisfy this round-trip for a value the callee never mutates. | |
| template <typename T> | |
| void ReverseTransform(const T &original_arg, | |
| python::PythonObject transformed_arg, Status &error) {} | |
| void ReverseTransform(bool &original_arg, | |
| python::PythonObject transformed_arg, Status &error) { | |
| python::PythonBoolean boolean_arg = python::PythonBoolean( | |
| python::PyRefType::Borrowed, transformed_arg.get()); | |
| if (boolean_arg.IsValid()) | |
| original_arg = boolean_arg.GetValue(); | |
| else | |
| error = Status::FromErrorStringWithFormatv( | |
| "{}: Invalid boolean argument.", LLVM_PRETTY_FUNCTION); | |
| } | |
| template <std::size_t... I, typename... Args> | |
| auto TransformTuple(const std::tuple<Args...> &args, | |
| std::index_sequence<I...>) { | |
| return std::make_tuple(Transform(std::get<I>(args))...); | |
| } | |
| // This will iterate over the Dispatch parameter pack and replace in-place | |
| // every `lldb_private` argument that has a SB counterpart. | |
| template <typename... Args> | |
| auto TransformArgs(const std::tuple<Args...> &args) { | |
| return TransformTuple(args, std::make_index_sequence<sizeof...(Args)>()); | |
| } | |
| // Apply `fn` with the first `N` elements of `t`, for compile-time `N`. | |
| template <std::size_t N, typename Tuple, typename Fn, std::size_t... I> | |
| static void ApplyPrefixImpl(Tuple &&t, Fn &&fn, std::index_sequence<I...>) { | |
| std::forward<Fn>(fn)(std::get<I>(std::forward<Tuple>(t))...); | |
| } | |
| template <std::size_t N, typename Tuple, typename Fn> | |
| static void ApplyPrefix(Tuple &&t, Fn &&fn) { | |
| ApplyPrefixImpl<N>(std::forward<Tuple>(t), std::forward<Fn>(fn), | |
| std::make_index_sequence<N>{}); | |
| } | |
| // Call `fn` with a runtime-selected prefix of `t`: exactly `call_arity` | |
| // leading elements. `Is...` enumerates every compile-time count in | |
| // `[0, sizeof...(Args)]`; the runtime check picks the matching one. | |
| template <typename Tuple, std::size_t... Is, typename Fn> | |
| static void CallWithArity(size_t call_arity, Tuple &&t, | |
| std::index_sequence<Is...>, Fn &&fn) { | |
| (void)std::initializer_list<int>{( | |
| Is == call_arity | |
| ? (ApplyPrefix<Is>(std::forward<Tuple>(t), std::forward<Fn>(fn)), 0) | |
| : 0)...}; | |
| } | |
| template <typename T, typename U> | |
| void TransformBack(T &original_arg, U transformed_arg, Status &error) { | |
| ReverseTransform(original_arg, transformed_arg, error); | |
| } | |
| // ScopedPythonObject is non-copyable — passing it through the generic | |
| // TransformBack would trigger the deleted copy ctor. It manages its own | |
| // cleanup via the destructor when the transformed-args tuple destructs, so | |
| // there is nothing to reverse-transform back into the original arg. | |
| template <typename T, typename SB> | |
| void TransformBack(T &original_arg, | |
| python::ScopedPythonObject<SB> &transformed_arg, | |
| Status &error) {} | |
| template <std::size_t... I, typename... Ts, typename... Us> | |
| bool ReassignPtrsOrRefsArgs(std::tuple<Ts...> &original_args, | |
| std::tuple<Us...> &transformed_args, | |
| std::index_sequence<I...>) { | |
| Status error; | |
| (TransformBack(std::get<I>(original_args), std::get<I>(transformed_args), | |
| error), | |
| ...); | |
| return error.Success(); | |
| } | |
| template <typename... Ts, typename... Us> | |
| bool ReassignPtrsOrRefsArgs(std::tuple<Ts...> &original_args, | |
| std::tuple<Us...> &transformed_args) { | |
| if (sizeof...(Ts) != sizeof...(Us)) | |
| return false; | |
| return ReassignPtrsOrRefsArgs(original_args, transformed_args, | |
| std::make_index_sequence<sizeof...(Ts)>()); | |
| } | |
| template <typename T, typename... Args> | |
| void FormatArgs(std::string &fmt, T arg, Args... args) const { | |
| FormatArgs(fmt, arg); | |
| FormatArgs(fmt, args...); | |
| } | |
| template <typename T> void FormatArgs(std::string &fmt, T arg) const { | |
| fmt += python::PythonFormat<T>::format; | |
| } | |
| void FormatArgs(std::string &fmt) const {} | |
| // The lifetime is managed by the ScriptInterpreter | |
| ScriptInterpreterPythonImpl &m_interpreter; | |
| }; | |
| template <> | |
| llvm::Expected<StructuredData::ArraySP> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<StructuredData::ArraySP>( | |
| python::PythonObject &p); | |
| template <> | |
| llvm::Expected<StructuredData::DictionarySP> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject< | |
| StructuredData::DictionarySP>(python::PythonObject &p); | |
| template <> | |
| llvm::Expected<Status> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<Status>( | |
| python::PythonObject &p); | |
| template <> | |
| llvm::Expected<Event *> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<Event *>( | |
| python::PythonObject &p); | |
| template <> | |
| llvm::Expected<SymbolContext> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<SymbolContext>( | |
| python::PythonObject &p); | |
| template <> | |
| llvm::Expected<lldb::StreamSP> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StreamSP>( | |
| python::PythonObject &p); | |
| template <> | |
| llvm::Expected<lldb::ThreadSP> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ThreadSP>( | |
| python::PythonObject &p); | |
| template <> | |
| llvm::Expected<lldb::StackFrameSP> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameSP>( | |
| python::PythonObject &p); | |
| template <> | |
| llvm::Expected<lldb::BreakpointSP> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::BreakpointSP>( | |
| python::PythonObject &p); | |
| template <> | |
| llvm::Expected<lldb::BreakpointLocationSP> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject< | |
| lldb::BreakpointLocationSP>(python::PythonObject &p); | |
| template <> | |
| llvm::Expected<lldb::ProcessAttachInfoSP> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject< | |
| lldb::ProcessAttachInfoSP>(python::PythonObject &p); | |
| template <> | |
| llvm::Expected<lldb::ProcessLaunchInfoSP> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject< | |
| lldb::ProcessLaunchInfoSP>(python::PythonObject &p); | |
| template <> | |
| llvm::Expected<lldb::DataExtractorSP> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DataExtractorSP>( | |
| python::PythonObject &p); | |
| template <> | |
| llvm::Expected<lldb::ThreadPlanSP> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ThreadPlanSP>( | |
| python::PythonObject &p); | |
| template <> | |
| llvm::Expected<std::optional<MemoryRegionInfo>> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject< | |
| std::optional<MemoryRegionInfo>>(python::PythonObject &p); | |
| template <> | |
| llvm::Expected<lldb::ExecutionContextRefSP> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject< | |
| lldb::ExecutionContextRefSP>(python::PythonObject &p); | |
| template <> | |
| llvm::Expected<lldb::DescriptionLevel> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DescriptionLevel>( | |
| python::PythonObject &p); | |
| template <> | |
| llvm::Expected<lldb::StepType> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StepType>( | |
| python::PythonObject &p); | |
| template <> | |
| llvm::Expected<lldb::StackFrameListSP> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::StackFrameListSP>( | |
| python::PythonObject &p); | |
| template <> | |
| llvm::Expected<lldb::ValueObjectSP> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ValueObjectSP>( | |
| python::PythonObject &p); | |
| template <> | |
| llvm::Expected<lldb::TargetSP> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::TargetSP>( | |
| python::PythonObject &p); | |
| template <> | |
| llvm::Expected<lldb::ValueObjectListSP> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::ValueObjectListSP>( | |
| python::PythonObject &p); | |
| template <> | |
| llvm::Expected<std::optional<lldb::ValueType>> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject< | |
| std::optional<lldb::ValueType>>(python::PythonObject &p); | |
| template <> | |
| llvm::Expected<lldb::DebuggerSP> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<lldb::DebuggerSP>( | |
| python::PythonObject &p); | |
| template <> | |
| llvm::Expected<std::vector<std::string>> | |
| ScriptedPythonInterface::ExtractValueFromPythonObject<std::vector<std::string>>( | |
| python::PythonObject &p); | |
| } // namespace lldb_private | |
| #endif // LLDB_SOURCE_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDPYTHONINTERFACE_H |