Skip to content

[lldb] Check for abstract methods implementation in Scripted Plugin Objects #71260

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class ScriptedInterface {
return m_object_instance_sp;
}

virtual llvm::SmallVector<llvm::StringLiteral> GetAbstractMethods() const = 0;

template <typename Ret>
static Ret ErrorWithMessage(llvm::StringRef caller_name,
llvm::StringRef error_msg, Status &error,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ class ScriptedPlatformInterface : virtual public ScriptedInterface {
StructuredData::DictionarySP args_sp,
StructuredData::Generic *script_obj = nullptr) = 0;

llvm::SmallVector<llvm::StringLiteral> GetAbstractMethods() const override {
return {};
}

virtual StructuredData::DictionarySP ListProcesses() { return {}; }

virtual StructuredData::DictionarySP GetProcessInfo(lldb::pid_t) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ class ScriptedProcessInterface : virtual public ScriptedInterface {
StructuredData::DictionarySP args_sp,
StructuredData::Generic *script_obj = nullptr) = 0;

llvm::SmallVector<llvm::StringLiteral> GetAbstractMethods() const override {
return {};
}

virtual StructuredData::DictionarySP GetCapabilities() { return {}; }

virtual Status Attach(const ProcessAttachInfo &attach_info) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ class ScriptedThreadInterface : virtual public ScriptedInterface {
StructuredData::DictionarySP args_sp,
StructuredData::Generic *script_obj = nullptr) = 0;

llvm::SmallVector<llvm::StringLiteral> GetAbstractMethods() const override {
return {};
}

virtual lldb::tid_t GetThreadID() { return LLDB_INVALID_THREAD_ID; }

virtual std::optional<std::string> GetName() { return std::nullopt; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,13 @@ class OperatingSystemPythonInterface
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"
"get_register_data",
"get_stop_reason",
"get_register_context"});
}

StructuredData::DictionarySP CreateThread(lldb::tid_t tid,
lldb::addr_t context) override;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ class ScriptedPlatformPythonInterface : public ScriptedPlatformInterface,
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ class ScriptedProcessPythonInterface : public ScriptedProcessInterface,
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,27 +32,66 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
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,
StructuredData::Generic *script_obj, Args... args) {
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,
Expand All @@ -67,26 +106,23 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
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(),
Expand All @@ -99,25 +135,87 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
}

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)));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ class ScriptedThreadPythonInterface : public ScriptedThreadInterface,
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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,8 @@ class PythonDictionary : public TypedPythonObject<PythonDictionary> {

static bool Check(PyObject *py_obj);

bool HasKey(const llvm::Twine &key) const;

uint32_t GetSize() const;

PythonList GetKeys() const;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,55 @@ def move_blueprint_to_dsym(self, blueprint_name):
)
shutil.copy(blueprint_origin_path, blueprint_destination_path)

def test_missing_methods_scripted_register_context(self):
"""Test that we only instanciate scripted processes if they implement
all the required abstract methods."""
self.build()

os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"] = "1"

def cleanup():
del os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"]

self.addTearDownHook(cleanup)

target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
self.assertTrue(target, VALID_TARGET)
log_file = self.getBuildArtifact("script.log")
self.runCmd("log enable lldb script -f " + log_file)
self.assertTrue(os.path.isfile(log_file))
script_path = os.path.join(
self.getSourceDir(), "missing_methods_scripted_process.py"
)
self.runCmd("command script import " + script_path)

launch_info = lldb.SBLaunchInfo(None)
launch_info.SetProcessPluginName("ScriptedProcess")
launch_info.SetScriptedProcessClassName(
"missing_methods_scripted_process.MissingMethodsScriptedProcess"
)
error = lldb.SBError()

process = target.Launch(launch_info, error)

self.assertFailure(error)

with open(log_file, "r") as f:
log = f.read()

self.assertIn(
"Abstract method MissingMethodsScriptedProcess.read_memory_at_address not implemented",
log,
)
self.assertIn(
"Abstract method MissingMethodsScriptedProcess.is_alive not implemented",
log,
)
self.assertIn(
"Abstract method MissingMethodsScriptedProcess.get_scripted_thread_plugin not implemented",
log,
)

@skipUnlessDarwin
def test_invalid_scripted_register_context(self):
"""Test that we can launch an lldb scripted process with an invalid
Expand Down
Loading