diff options
author | Med Ismail Bennani <ismail@bennani.ma> | 2023-11-07 22:01:21 -0800 |
---|---|---|
committer | Med Ismail Bennani <ismail@bennani.ma> | 2023-11-07 22:01:41 -0800 |
commit | 0a21144614950ce063d8dac6394307bd3be604cd (patch) | |
tree | 4cd20f82195bfc848452b035cb7e4bd177ca555f /lldb/source/Plugins/ScriptInterpreter/Python | |
parent | 14e7846d6e2c5c99c52ba3882e59bfb021a5f0fa (diff) | |
download | llvm-0a21144614950ce063d8dac6394307bd3be604cd.zip llvm-0a21144614950ce063d8dac6394307bd3be604cd.tar.gz llvm-0a21144614950ce063d8dac6394307bd3be604cd.tar.bz2 |
[lldb] Check for abstract methods implementation in Scripted Plugin Objects (#71260)
This patch enforces that every scripted object implements all the
necessary abstract methods.
Every scripted affordance language interface can implement a list of
abstract methods name that checked when the object is instanciated.
Since some scripting affordances implementations can be derived from
template base classes, we can't check the object dictionary since it
will contain the definition of the base class, so instead, this checks
the scripting class dictionary.
Previously, for the various python interfaces, we used
`ABC.abstractmethod` decorators but this is too language specific and
doesn't work for scripting affordances that are not derived from
template base classes (i.e OperatingSystem, ScriptedThreadPlan, ...), so
this patch provides generic/language-agnostic checks for every scripted
affordance.
Signed-off-by: Med Ismail Bennani <ismail@bennani.ma>
Diffstat (limited to 'lldb/source/Plugins/ScriptInterpreter/Python')
7 files changed, 163 insertions, 29 deletions
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/OperatingSystemPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/OperatingSystemPythonInterface.h index ee24e0e..da7bbf1 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/OperatingSystemPythonInterface.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/OperatingSystemPythonInterface.h @@ -29,6 +29,10 @@ public: StructuredData::DictionarySP args_sp, StructuredData::Generic *script_obj = nullptr) override; + llvm::SmallVector<llvm::StringLiteral> GetAbstractMethods() const override { + return llvm::SmallVector<llvm::StringLiteral>({"get_thread_info"}); + } + StructuredData::DictionarySP CreateThread(lldb::tid_t tid, lldb::addr_t context) override; diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPlatformPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPlatformPythonInterface.h index e04f2d0..0842d3a 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPlatformPythonInterface.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPlatformPythonInterface.h @@ -28,6 +28,12 @@ public: StructuredData::DictionarySP args_sp, StructuredData::Generic *script_obj = nullptr) override; + llvm::SmallVector<llvm::StringLiteral> GetAbstractMethods() const override { + return llvm::SmallVector<llvm::StringLiteral>( + {"list_processes", "attach_to_process", "launch_process", + "kill_process"}); + } + StructuredData::DictionarySP ListProcesses() override; StructuredData::DictionarySP GetProcessInfo(lldb::pid_t) override; diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedProcessPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedProcessPythonInterface.h index f3cff61..c75caa9 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedProcessPythonInterface.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedProcessPythonInterface.h @@ -29,6 +29,11 @@ public: StructuredData::DictionarySP args_sp, StructuredData::Generic *script_obj = nullptr) override; + llvm::SmallVector<llvm::StringLiteral> GetAbstractMethods() const override { + return llvm::SmallVector<llvm::StringLiteral>( + {"read_memory_at_address", "is_alive", "get_scripted_thread_plugin"}); + } + StructuredData::DictionarySP GetCapabilities() override; Status Attach(const ProcessAttachInfo &attach_info) override; diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h index 7af9816..1636592 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h @@ -32,6 +32,45 @@ public: ScriptedPythonInterface(ScriptInterpreterPythonImpl &interpreter); ~ScriptedPythonInterface() override = default; + enum class AbstractMethodCheckerCases { + eNotImplemented, + eNotAllocated, + eNotCallable, + eValid + }; + + llvm::Expected<std::map<llvm::StringLiteral, AbstractMethodCheckerCases>> + CheckAbstractMethodImplementation( + const python::PythonDictionary &class_dict) const { + + using namespace python; + + std::map<llvm::StringLiteral, AbstractMethodCheckerCases> checker; +#define SET_ERROR_AND_CONTINUE(method_name, error) \ + { \ + checker[method_name] = error; \ + continue; \ + } + + for (const llvm::StringLiteral &method_name : GetAbstractMethods()) { + if (!class_dict.HasKey(method_name)) + SET_ERROR_AND_CONTINUE(method_name, + AbstractMethodCheckerCases::eNotImplemented) + auto callable_or_err = class_dict.GetItem(method_name); + if (!callable_or_err) + SET_ERROR_AND_CONTINUE(method_name, + AbstractMethodCheckerCases::eNotAllocated) + if (!PythonCallable::Check(callable_or_err.get().get())) + SET_ERROR_AND_CONTINUE(method_name, + AbstractMethodCheckerCases::eNotCallable) + checker[method_name] = AbstractMethodCheckerCases::eValid; + } + +#undef HANDLE_ERROR + + return checker; + } + template <typename... Args> llvm::Expected<StructuredData::GenericSP> CreatePluginObject(llvm::StringRef class_name, @@ -39,20 +78,20 @@ public: using namespace python; using Locker = ScriptInterpreterPythonImpl::Locker; + auto create_error = [](std::string message) { + return llvm::createStringError(llvm::inconvertibleErrorCode(), message); + }; + 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 llvm::createStringError(llvm::inconvertibleErrorCode(), - "Missing script class name."); + return create_error("Missing script class name."); else if (!has_interpreter_dict) - return llvm::createStringError( - llvm::inconvertibleErrorCode(), - "Invalid script interpreter dictionary."); + return create_error("Invalid script interpreter dictionary."); else - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "Missing scripting object."); + return create_error("Missing scripting object."); } Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN, @@ -67,26 +106,23 @@ public: auto dict = PythonModule::MainModule().ResolveName<python::PythonDictionary>( m_interpreter.GetDictionaryName()); - if (!dict.IsAllocated()) { - return llvm::createStringError( - llvm::inconvertibleErrorCode(), - "Could not find interpreter dictionary: %s", - m_interpreter.GetDictionaryName()); - } + if (!dict.IsAllocated()) + return create_error( + llvm::formatv("Could not find interpreter dictionary: %s", + m_interpreter.GetDictionaryName())); - auto method = + auto init = PythonObject::ResolveNameWithDictionary<python::PythonCallable>( class_name, dict); - if (!method.IsAllocated()) - return llvm::createStringError(llvm::inconvertibleErrorCode(), - "Could not find script class: %s", - class_name.data()); + if (!init.IsAllocated()) + return create_error(llvm::formatv("Could not find script class: %s", + 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 = method.GetArgInfo(); + llvm::Expected<PythonCallable::ArgInfo> arg_info = init.GetArgInfo(); if (!arg_info) { llvm::handleAllErrors( arg_info.takeError(), @@ -99,25 +135,87 @@ public: } llvm::Expected<PythonObject> expected_return_object = - llvm::createStringError(llvm::inconvertibleErrorCode(), - "Resulting object is not initialized."); + create_error("Resulting object is not initialized."); std::apply( - [&method, &expected_return_object](auto &&...args) { + [&init, &expected_return_object](auto &&...args) { llvm::consumeError(expected_return_object.takeError()); - expected_return_object = method(args...); + expected_return_object = init(args...); }, transformed_args); - if (llvm::Error e = expected_return_object.takeError()) - return std::move(e); - result = std::move(expected_return_object.get()); + if (!expected_return_object) + return expected_return_object.takeError(); + result = expected_return_object.get(); } if (!result.IsValid()) - return llvm::createStringError( - llvm::inconvertibleErrorCode(), - "Resulting object is not a valid Python Object."); + 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>(); + + 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<PythonCallable>(); + 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<PythonDictionary>(); + 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(); + + for (const auto &method_checker : *checker_or_err) + switch (method_checker.second) { + case AbstractMethodCheckerCases::eNotImplemented: + LLDB_LOG(GetLog(LLDBLog::Script), + "Abstract method {0}.{1} not implemented.", + obj_class_name.GetString(), method_checker.first); + break; + case AbstractMethodCheckerCases::eNotAllocated: + LLDB_LOG(GetLog(LLDBLog::Script), + "Abstract method {0}.{1} not allocated.", + obj_class_name.GetString(), method_checker.first); + break; + case AbstractMethodCheckerCases::eNotCallable: + LLDB_LOG(GetLog(LLDBLog::Script), + "Abstract method {0}.{1} not callable.", + obj_class_name.GetString(), method_checker.first); + break; + case AbstractMethodCheckerCases::eValid: + LLDB_LOG(GetLog(LLDBLog::Script), + "Abstract method {0}.{1} implemented & valid.", + obj_class_name.GetString(), method_checker.first); + break; + } + + for (const auto &method_checker : *checker_or_err) + if (method_checker.second != AbstractMethodCheckerCases::eValid) + return create_error( + llvm::formatv("Abstract method {0}.{1} missing. Enable lldb " + "script log for more details.", + obj_class_name.GetString(), method_checker.first)); m_object_instance_sp = StructuredData::GenericSP( new StructuredPythonObject(std::move(result))); diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPythonInterface.h index b7b7439..5676f7f 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPythonInterface.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPythonInterface.h @@ -28,6 +28,11 @@ public: StructuredData::DictionarySP args_sp, StructuredData::Generic *script_obj = nullptr) override; + llvm::SmallVector<llvm::StringLiteral> GetAbstractMethods() const override { + return llvm::SmallVector<llvm::StringLiteral>( + {"get_stop_reason", "get_register_context"}); + } + lldb::tid_t GetThreadID() override; std::optional<std::string> GetName() override; diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp index fe3438c..ea0a1cd 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp @@ -663,6 +663,20 @@ bool PythonDictionary::Check(PyObject *py_obj) { return PyDict_Check(py_obj); } +bool PythonDictionary::HasKey(const llvm::Twine &key) const { + if (!IsValid()) + return false; + + PythonString key_object(key.isSingleStringRef() ? key.getSingleStringRef() + : key.str()); + + if (int res = PyDict_Contains(m_py_obj, key_object.get()) > 0) + return res; + + PyErr_Print(); + return false; +} + uint32_t PythonDictionary::GetSize() const { if (IsValid()) return PyDict_Size(m_py_obj); diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h index 012f16e..82eee76 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h @@ -562,6 +562,8 @@ public: static bool Check(PyObject *py_obj); + bool HasKey(const llvm::Twine &key) const; + uint32_t GetSize() const; PythonList GetKeys() const; |