//===-- 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_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDPYTHONINTERFACE_H #define LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDPYTHONINTERFACE_H #if LLDB_ENABLE_PYTHON #include #include #include #include #include #include "lldb/Host/Config.h" #include "lldb/Interpreter/Interfaces/ScriptedInterface.h" #include "lldb/Utility/DataBufferHeap.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 AbstrackMethodCheckerPayload { 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 payload; }; llvm::Expected> CheckAbstractMethodImplementation( const python::PythonDictionary &class_dict) const { using namespace python; std::map 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; if (!class_dict.HasKey(method_name)) SET_CASE_AND_CONTINUE(method_name, AbstractMethodCheckerCases::eNotImplemented) auto callable_or_err = class_dict.GetItem(method_name); if (!callable_or_err) { llvm::consumeError(callable_or_err.takeError()); SET_CASE_AND_CONTINUE(method_name, AbstractMethodCheckerCases::eNotAllocated) } PythonCallable callable = callable_or_err->AsType(); 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) { llvm::consumeError(arg_info_or_err.takeError()); SET_CASE_AND_CONTINUE(method_name, AbstractMethodCheckerCases::eUnknownArgumentCount) } 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, AbstrackMethodCheckerPayload::InvalidArgumentCountPayload( requirement.min_arg_count, arg_info.max_positional_args)}; } } #undef SET_CASE_AND_CONTINUE return checker; } template llvm::Expected CreatePluginObject(llvm::StringRef class_name, StructuredData::Generic *script_obj, Args... args) { using namespace python; using Locker = ScriptInterpreterPythonImpl::Locker; Log *log = GetLog(LLDBLog::Script); auto create_error = [](llvm::StringLiteral format, auto &&...ts) { return llvm::createStringError( llvm::formatv(format.data(), std::forward(ts)...) .str()); }; 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."); } Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock); PythonObject result = {}; if (script_obj) { result = PythonObject(PyRefType::Borrowed, static_cast(script_obj->GetValue())); } else { auto dict = PythonModule::MainModule().ResolveName( m_interpreter.GetDictionaryName()); if (!dict.IsAllocated()) return create_error("Could not find interpreter dictionary: {0}", m_interpreter.GetDictionaryName()); auto init = PythonObject::ResolveNameWithDictionary( class_name, dict); if (!init.IsAllocated()) return create_error("Could not find script class: {0}", class_name.data()); std::tuple original_args = std::forward_as_tuple(args...); auto transformed_args = TransformArgs(original_args); std::string error_string; llvm::Expected 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); } llvm::Expected 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. size_t num_args = sizeof...(Args); if (num_args != arg_info->max_positional_args) { if (num_args != arg_info->max_positional_args - 1) 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) { llvm::consumeError(expected_return_object.takeError()); expected_return_object = init(args...); }, std::tuple_cat(transformed_args, std::make_tuple(dict))); } else { std::apply( [&init, &expected_return_object](auto &&...args) { llvm::consumeError(expected_return_object.takeError()); expected_return_object = init(args...); }, transformed_args); } if (!expected_return_object) return 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(); PythonObject object_class_mapping_proxy = obj_class.GetAttributeValue("__dict__"); if (!obj_class.HasAttribute("__dict__")) return create_error( "Resulting object class doesn't have '__dict__' member."); PythonCallable dict_converter = PythonModule::BuiltinsModule() .ResolveName("dict") .AsType(); if (!dict_converter.IsAllocated()) return create_error( "Python 'builtins' module doesn't have 'dict' class."); PythonDictionary object_class_dict = dict_converter(object_class_mapping_proxy).AsType(); if (!object_class_dict.IsAllocated()) return create_error("Coudn't create dictionary from resulting object " "class mapping proxy object."); auto checker_or_err = CheckAbstractMethodImplementation(object_class_dict); 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), std::move(create_error("Abstract method {0}.{1} not implemented.", obj_class_name.GetString(), method_checker.first))); break; case AbstractMethodCheckerCases::eNotAllocated: abstract_method_errors = llvm::joinErrors( std::move(abstract_method_errors), std::move(create_error("Abstract method {0}.{1} not allocated.", obj_class_name.GetString(), method_checker.first))); break; case AbstractMethodCheckerCases::eNotCallable: abstract_method_errors = llvm::joinErrors( std::move(abstract_method_errors), std::move(create_error("Abstract method {0}.{1} not callable.", obj_class_name.GetString(), method_checker.first))); break; case AbstractMethodCheckerCases::eUnknownArgumentCount: abstract_method_errors = llvm::joinErrors( std::move(abstract_method_errors), std::move(create_error( "Abstract method {0}.{1} has unknown argument count.", obj_class_name.GetString(), method_checker.first))); break; case AbstractMethodCheckerCases::eInvalidArgumentCount: { auto &payload_variant = method_checker.second.payload; if (!std::holds_alternative< AbstrackMethodCheckerPayload::InvalidArgumentCountPayload>( payload_variant)) { abstract_method_errors = llvm::joinErrors( std::move(abstract_method_errors), std::move(create_error( "Abstract method {0}.{1} has unexpected argument count.", obj_class_name.GetString(), method_checker.first))); } else { auto payload = std::get< AbstrackMethodCheckerPayload::InvalidArgumentCountPayload>( payload_variant); abstract_method_errors = llvm::joinErrors( std::move(abstract_method_errors), std::move( create_error("Abstract method {0}.{1} has unexpected " "argument count (expected {2} but has {3}).", obj_class_name.GetString(), method_checker.first, payload.required_argument_count, payload.actual_argument_count))); } } break; case AbstractMethodCheckerCases::eValid: LLDB_LOG(log, "Abstract method {0}.{1} implemented & valid.", obj_class_name.GetString(), 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}", class_name, error.AsCString()); return error.ToError(); } m_object_instance_sp = StructuredData::GenericSP( new StructuredPythonObject(std::move(result))); return m_object_instance_sp; } protected: template T ExtractValueFromPythonObject(python::PythonObject &p, Status &error) { return p.CreateStructuredObject(); } template T Dispatch(llvm::StringRef method_name, Status &error, 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 ErrorWithMessage(caller_signature, "Python object ill-formed", error); 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 llvm::is_contained(GetAbstractMethods(), method_name) ? ErrorWithMessage(caller_signature, "Python implementor not allocated.", error) : T{}; std::tuple original_args = std::forward_as_tuple(args...); auto transformed_args = TransformArgs(original_args); llvm::Expected expected_return_object = llvm::make_error("Not initialized.", llvm::inconvertibleErrorCode()); std::apply( [&implementor, &method_name, &expected_return_object](auto &&...args) { llvm::consumeError(expected_return_object.takeError()); expected_return_object = implementor.CallMethod(method_name.data(), args...); }, transformed_args); if (llvm::Error e = expected_return_object.takeError()) { error = Status::FromError(std::move(e)); return ErrorWithMessage(caller_signature, "Python method could not be called.", error); } PythonObject py_return = std::move(expected_return_object.get()); // Now that we called the python method with the transformed arguments, // we need to interate 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 ErrorWithMessage( caller_signature, "Couldn't re-assign reference and pointer arguments.", error); if (!py_return.IsAllocated()) return {}; return ExtractValueFromPythonObject(py_return, error); } template Status GetStatusFromMethod(llvm::StringRef method_name, Args &&...args) { Status error; Dispatch(method_name, error, std::forward(args)...); return error; } template 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); } 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::ProcessSP 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(lldb::StreamSP arg) { return python::SWIGBridge::ToSWIGWrapper(arg.get()); } python::PythonObject Transform(lldb::DataExtractorSP arg) { return python::SWIGBridge::ToSWIGWrapper(arg); } template void ReverseTransform(T &original_arg, U transformed_arg, Status &error) { // If U is not a PythonObject, don't touch it! return; } template void ReverseTransform(T &original_arg, python::PythonObject transformed_arg, Status &error) { original_arg = ExtractValueFromPythonObject(transformed_arg, 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 auto TransformTuple(const std::tuple &args, std::index_sequence) { return std::make_tuple(Transform(std::get(args))...); } // This will iterate over the Dispatch parameter pack and replace in-place // every `lldb_private` argument that has a SB counterpart. template auto TransformArgs(const std::tuple &args) { return TransformTuple(args, std::make_index_sequence()); } template void TransformBack(T &original_arg, U transformed_arg, Status &error) { ReverseTransform(original_arg, transformed_arg, error); } template bool ReassignPtrsOrRefsArgs(std::tuple &original_args, std::tuple &transformed_args, std::index_sequence) { Status error; (TransformBack(std::get(original_args), std::get(transformed_args), error), ...); return error.Success(); } template bool ReassignPtrsOrRefsArgs(std::tuple &original_args, std::tuple &transformed_args) { if (sizeof...(Ts) != sizeof...(Us)) return false; return ReassignPtrsOrRefsArgs(original_args, transformed_args, std::make_index_sequence()); } template void FormatArgs(std::string &fmt, T arg, Args... args) const { FormatArgs(fmt, arg); FormatArgs(fmt, args...); } template void FormatArgs(std::string &fmt, T arg) const { fmt += python::PythonFormat::format; } void FormatArgs(std::string &fmt) const {} // The lifetime is managed by the ScriptInterpreter ScriptInterpreterPythonImpl &m_interpreter; }; template <> StructuredData::ArraySP ScriptedPythonInterface::ExtractValueFromPythonObject( python::PythonObject &p, Status &error); template <> StructuredData::DictionarySP ScriptedPythonInterface::ExtractValueFromPythonObject< StructuredData::DictionarySP>(python::PythonObject &p, Status &error); template <> Status ScriptedPythonInterface::ExtractValueFromPythonObject( python::PythonObject &p, Status &error); template <> Event *ScriptedPythonInterface::ExtractValueFromPythonObject( python::PythonObject &p, Status &error); template <> lldb::StreamSP ScriptedPythonInterface::ExtractValueFromPythonObject( python::PythonObject &p, Status &error); template <> lldb::BreakpointSP ScriptedPythonInterface::ExtractValueFromPythonObject( python::PythonObject &p, Status &error); template <> lldb::ProcessAttachInfoSP ScriptedPythonInterface::ExtractValueFromPythonObject< lldb::ProcessAttachInfoSP>(python::PythonObject &p, Status &error); template <> lldb::ProcessLaunchInfoSP ScriptedPythonInterface::ExtractValueFromPythonObject< lldb::ProcessLaunchInfoSP>(python::PythonObject &p, Status &error); template <> lldb::DataExtractorSP ScriptedPythonInterface::ExtractValueFromPythonObject( python::PythonObject &p, Status &error); template <> std::optional ScriptedPythonInterface::ExtractValueFromPythonObject< std::optional>(python::PythonObject &p, Status &error); template <> lldb::ExecutionContextRefSP ScriptedPythonInterface::ExtractValueFromPythonObject< lldb::ExecutionContextRefSP>(python::PythonObject &p, Status &error); } // namespace lldb_private #endif // LLDB_ENABLE_PYTHON #endif // LLDB_PLUGINS_SCRIPTINTERPRETER_PYTHON_INTERFACES_SCRIPTEDPYTHONINTERFACE_H