Skip to content

Commit 0a21144

Browse files
committed
[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 <[email protected]>
1 parent 14e7846 commit 0a21144

File tree

11 files changed

+236
-29
lines changed

11 files changed

+236
-29
lines changed

lldb/include/lldb/Interpreter/Interfaces/ScriptedInterface.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ class ScriptedInterface {
3030
return m_object_instance_sp;
3131
}
3232

33+
virtual llvm::SmallVector<llvm::StringLiteral> GetAbstractMethods() const = 0;
34+
3335
template <typename Ret>
3436
static Ret ErrorWithMessage(llvm::StringRef caller_name,
3537
llvm::StringRef error_msg, Status &error,

lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/OperatingSystemPythonInterface.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ class OperatingSystemPythonInterface
2929
StructuredData::DictionarySP args_sp,
3030
StructuredData::Generic *script_obj = nullptr) override;
3131

32+
llvm::SmallVector<llvm::StringLiteral> GetAbstractMethods() const override {
33+
return llvm::SmallVector<llvm::StringLiteral>({"get_thread_info"});
34+
}
35+
3236
StructuredData::DictionarySP CreateThread(lldb::tid_t tid,
3337
lldb::addr_t context) override;
3438

lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPlatformPythonInterface.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ class ScriptedPlatformPythonInterface : public ScriptedPlatformInterface,
2828
StructuredData::DictionarySP args_sp,
2929
StructuredData::Generic *script_obj = nullptr) override;
3030

31+
llvm::SmallVector<llvm::StringLiteral> GetAbstractMethods() const override {
32+
return llvm::SmallVector<llvm::StringLiteral>(
33+
{"list_processes", "attach_to_process", "launch_process",
34+
"kill_process"});
35+
}
36+
3137
StructuredData::DictionarySP ListProcesses() override;
3238

3339
StructuredData::DictionarySP GetProcessInfo(lldb::pid_t) override;

lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedProcessPythonInterface.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ class ScriptedProcessPythonInterface : public ScriptedProcessInterface,
2929
StructuredData::DictionarySP args_sp,
3030
StructuredData::Generic *script_obj = nullptr) override;
3131

32+
llvm::SmallVector<llvm::StringLiteral> GetAbstractMethods() const override {
33+
return llvm::SmallVector<llvm::StringLiteral>(
34+
{"read_memory_at_address", "is_alive", "get_scripted_thread_plugin"});
35+
}
36+
3237
StructuredData::DictionarySP GetCapabilities() override;
3338

3439
Status Attach(const ProcessAttachInfo &attach_info) override;

lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h

Lines changed: 127 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,66 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
3232
ScriptedPythonInterface(ScriptInterpreterPythonImpl &interpreter);
3333
~ScriptedPythonInterface() override = default;
3434

35+
enum class AbstractMethodCheckerCases {
36+
eNotImplemented,
37+
eNotAllocated,
38+
eNotCallable,
39+
eValid
40+
};
41+
42+
llvm::Expected<std::map<llvm::StringLiteral, AbstractMethodCheckerCases>>
43+
CheckAbstractMethodImplementation(
44+
const python::PythonDictionary &class_dict) const {
45+
46+
using namespace python;
47+
48+
std::map<llvm::StringLiteral, AbstractMethodCheckerCases> checker;
49+
#define SET_ERROR_AND_CONTINUE(method_name, error) \
50+
{ \
51+
checker[method_name] = error; \
52+
continue; \
53+
}
54+
55+
for (const llvm::StringLiteral &method_name : GetAbstractMethods()) {
56+
if (!class_dict.HasKey(method_name))
57+
SET_ERROR_AND_CONTINUE(method_name,
58+
AbstractMethodCheckerCases::eNotImplemented)
59+
auto callable_or_err = class_dict.GetItem(method_name);
60+
if (!callable_or_err)
61+
SET_ERROR_AND_CONTINUE(method_name,
62+
AbstractMethodCheckerCases::eNotAllocated)
63+
if (!PythonCallable::Check(callable_or_err.get().get()))
64+
SET_ERROR_AND_CONTINUE(method_name,
65+
AbstractMethodCheckerCases::eNotCallable)
66+
checker[method_name] = AbstractMethodCheckerCases::eValid;
67+
}
68+
69+
#undef HANDLE_ERROR
70+
71+
return checker;
72+
}
73+
3574
template <typename... Args>
3675
llvm::Expected<StructuredData::GenericSP>
3776
CreatePluginObject(llvm::StringRef class_name,
3877
StructuredData::Generic *script_obj, Args... args) {
3978
using namespace python;
4079
using Locker = ScriptInterpreterPythonImpl::Locker;
4180

81+
auto create_error = [](std::string message) {
82+
return llvm::createStringError(llvm::inconvertibleErrorCode(), message);
83+
};
84+
4285
bool has_class_name = !class_name.empty();
4386
bool has_interpreter_dict =
4487
!(llvm::StringRef(m_interpreter.GetDictionaryName()).empty());
4588
if (!has_class_name && !has_interpreter_dict && !script_obj) {
4689
if (!has_class_name)
47-
return llvm::createStringError(llvm::inconvertibleErrorCode(),
48-
"Missing script class name.");
90+
return create_error("Missing script class name.");
4991
else if (!has_interpreter_dict)
50-
return llvm::createStringError(
51-
llvm::inconvertibleErrorCode(),
52-
"Invalid script interpreter dictionary.");
92+
return create_error("Invalid script interpreter dictionary.");
5393
else
54-
return llvm::createStringError(llvm::inconvertibleErrorCode(),
55-
"Missing scripting object.");
94+
return create_error("Missing scripting object.");
5695
}
5796

5897
Locker py_lock(&m_interpreter, Locker::AcquireLock | Locker::NoSTDIN,
@@ -67,26 +106,23 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
67106
auto dict =
68107
PythonModule::MainModule().ResolveName<python::PythonDictionary>(
69108
m_interpreter.GetDictionaryName());
70-
if (!dict.IsAllocated()) {
71-
return llvm::createStringError(
72-
llvm::inconvertibleErrorCode(),
73-
"Could not find interpreter dictionary: %s",
74-
m_interpreter.GetDictionaryName());
75-
}
109+
if (!dict.IsAllocated())
110+
return create_error(
111+
llvm::formatv("Could not find interpreter dictionary: %s",
112+
m_interpreter.GetDictionaryName()));
76113

77-
auto method =
114+
auto init =
78115
PythonObject::ResolveNameWithDictionary<python::PythonCallable>(
79116
class_name, dict);
80-
if (!method.IsAllocated())
81-
return llvm::createStringError(llvm::inconvertibleErrorCode(),
82-
"Could not find script class: %s",
83-
class_name.data());
117+
if (!init.IsAllocated())
118+
return create_error(llvm::formatv("Could not find script class: %s",
119+
class_name.data()));
84120

85121
std::tuple<Args...> original_args = std::forward_as_tuple(args...);
86122
auto transformed_args = TransformArgs(original_args);
87123

88124
std::string error_string;
89-
llvm::Expected<PythonCallable::ArgInfo> arg_info = method.GetArgInfo();
125+
llvm::Expected<PythonCallable::ArgInfo> arg_info = init.GetArgInfo();
90126
if (!arg_info) {
91127
llvm::handleAllErrors(
92128
arg_info.takeError(),
@@ -99,25 +135,87 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
99135
}
100136

101137
llvm::Expected<PythonObject> expected_return_object =
102-
llvm::createStringError(llvm::inconvertibleErrorCode(),
103-
"Resulting object is not initialized.");
138+
create_error("Resulting object is not initialized.");
104139

105140
std::apply(
106-
[&method, &expected_return_object](auto &&...args) {
141+
[&init, &expected_return_object](auto &&...args) {
107142
llvm::consumeError(expected_return_object.takeError());
108-
expected_return_object = method(args...);
143+
expected_return_object = init(args...);
109144
},
110145
transformed_args);
111146

112-
if (llvm::Error e = expected_return_object.takeError())
113-
return std::move(e);
114-
result = std::move(expected_return_object.get());
147+
if (!expected_return_object)
148+
return expected_return_object.takeError();
149+
result = expected_return_object.get();
115150
}
116151

117152
if (!result.IsValid())
118-
return llvm::createStringError(
119-
llvm::inconvertibleErrorCode(),
120-
"Resulting object is not a valid Python Object.");
153+
return create_error("Resulting object is not a valid Python Object.");
154+
if (!result.HasAttribute("__class__"))
155+
return create_error("Resulting object doesn't have '__class__' member.");
156+
157+
PythonObject obj_class = result.GetAttributeValue("__class__");
158+
if (!obj_class.IsValid())
159+
return create_error("Resulting class object is not a valid.");
160+
if (!obj_class.HasAttribute("__name__"))
161+
return create_error(
162+
"Resulting object class doesn't have '__name__' member.");
163+
PythonString obj_class_name =
164+
obj_class.GetAttributeValue("__name__").AsType<PythonString>();
165+
166+
PythonObject object_class_mapping_proxy =
167+
obj_class.GetAttributeValue("__dict__");
168+
if (!obj_class.HasAttribute("__dict__"))
169+
return create_error(
170+
"Resulting object class doesn't have '__dict__' member.");
171+
172+
PythonCallable dict_converter = PythonModule::BuiltinsModule()
173+
.ResolveName("dict")
174+
.AsType<PythonCallable>();
175+
if (!dict_converter.IsAllocated())
176+
return create_error(
177+
"Python 'builtins' module doesn't have 'dict' class.");
178+
179+
PythonDictionary object_class_dict =
180+
dict_converter(object_class_mapping_proxy).AsType<PythonDictionary>();
181+
if (!object_class_dict.IsAllocated())
182+
return create_error("Coudn't create dictionary from resulting object "
183+
"class mapping proxy object.");
184+
185+
auto checker_or_err = CheckAbstractMethodImplementation(object_class_dict);
186+
if (!checker_or_err)
187+
return checker_or_err.takeError();
188+
189+
for (const auto &method_checker : *checker_or_err)
190+
switch (method_checker.second) {
191+
case AbstractMethodCheckerCases::eNotImplemented:
192+
LLDB_LOG(GetLog(LLDBLog::Script),
193+
"Abstract method {0}.{1} not implemented.",
194+
obj_class_name.GetString(), method_checker.first);
195+
break;
196+
case AbstractMethodCheckerCases::eNotAllocated:
197+
LLDB_LOG(GetLog(LLDBLog::Script),
198+
"Abstract method {0}.{1} not allocated.",
199+
obj_class_name.GetString(), method_checker.first);
200+
break;
201+
case AbstractMethodCheckerCases::eNotCallable:
202+
LLDB_LOG(GetLog(LLDBLog::Script),
203+
"Abstract method {0}.{1} not callable.",
204+
obj_class_name.GetString(), method_checker.first);
205+
break;
206+
case AbstractMethodCheckerCases::eValid:
207+
LLDB_LOG(GetLog(LLDBLog::Script),
208+
"Abstract method {0}.{1} implemented & valid.",
209+
obj_class_name.GetString(), method_checker.first);
210+
break;
211+
}
212+
213+
for (const auto &method_checker : *checker_or_err)
214+
if (method_checker.second != AbstractMethodCheckerCases::eValid)
215+
return create_error(
216+
llvm::formatv("Abstract method {0}.{1} missing. Enable lldb "
217+
"script log for more details.",
218+
obj_class_name.GetString(), method_checker.first));
121219

122220
m_object_instance_sp = StructuredData::GenericSP(
123221
new StructuredPythonObject(std::move(result)));

lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedThreadPythonInterface.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ class ScriptedThreadPythonInterface : public ScriptedThreadInterface,
2828
StructuredData::DictionarySP args_sp,
2929
StructuredData::Generic *script_obj = nullptr) override;
3030

31+
llvm::SmallVector<llvm::StringLiteral> GetAbstractMethods() const override {
32+
return llvm::SmallVector<llvm::StringLiteral>(
33+
{"get_stop_reason", "get_register_context"});
34+
}
35+
3136
lldb::tid_t GetThreadID() override;
3237

3338
std::optional<std::string> GetName() override;

lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.cpp

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -663,6 +663,20 @@ bool PythonDictionary::Check(PyObject *py_obj) {
663663
return PyDict_Check(py_obj);
664664
}
665665

666+
bool PythonDictionary::HasKey(const llvm::Twine &key) const {
667+
if (!IsValid())
668+
return false;
669+
670+
PythonString key_object(key.isSingleStringRef() ? key.getSingleStringRef()
671+
: key.str());
672+
673+
if (int res = PyDict_Contains(m_py_obj, key_object.get()) > 0)
674+
return res;
675+
676+
PyErr_Print();
677+
return false;
678+
}
679+
666680
uint32_t PythonDictionary::GetSize() const {
667681
if (IsValid())
668682
return PyDict_Size(m_py_obj);

lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -562,6 +562,8 @@ class PythonDictionary : public TypedPythonObject<PythonDictionary> {
562562

563563
static bool Check(PyObject *py_obj);
564564

565+
bool HasKey(const llvm::Twine &key) const;
566+
565567
uint32_t GetSize() const;
566568

567569
PythonList GetKeys() const;

lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,55 @@ def move_blueprint_to_dsym(self, blueprint_name):
6060
)
6161
shutil.copy(blueprint_origin_path, blueprint_destination_path)
6262

63+
def test_missing_methods_scripted_register_context(self):
64+
"""Test that we only instanciate scripted processes if they implement
65+
all the required abstract methods."""
66+
self.build()
67+
68+
os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"] = "1"
69+
70+
def cleanup():
71+
del os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"]
72+
73+
self.addTearDownHook(cleanup)
74+
75+
target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
76+
self.assertTrue(target, VALID_TARGET)
77+
log_file = self.getBuildArtifact("script.log")
78+
self.runCmd("log enable lldb script -f " + log_file)
79+
self.assertTrue(os.path.isfile(log_file))
80+
script_path = os.path.join(
81+
self.getSourceDir(), "missing_methods_scripted_process.py"
82+
)
83+
self.runCmd("command script import " + script_path)
84+
85+
launch_info = lldb.SBLaunchInfo(None)
86+
launch_info.SetProcessPluginName("ScriptedProcess")
87+
launch_info.SetScriptedProcessClassName(
88+
"missing_methods_scripted_process.MissingMethodsScriptedProcess"
89+
)
90+
error = lldb.SBError()
91+
92+
process = target.Launch(launch_info, error)
93+
94+
self.assertFailure(error)
95+
96+
with open(log_file, "r") as f:
97+
log = f.read()
98+
99+
self.assertIn(
100+
"Abstract method MissingMethodsScriptedProcess.read_memory_at_address not implemented",
101+
log,
102+
)
103+
self.assertIn(
104+
"Abstract method MissingMethodsScriptedProcess.is_alive not implemented",
105+
log,
106+
)
107+
self.assertIn(
108+
"Abstract method MissingMethodsScriptedProcess.get_scripted_thread_plugin not implemented",
109+
log,
110+
)
111+
63112
@skipUnlessDarwin
64113
def test_invalid_scripted_register_context(self):
65114
"""Test that we can launch an lldb scripted process with an invalid
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import os
2+
3+
4+
class MissingMethodsScriptedProcess:
5+
def __init__(self, exe_ctx, args):
6+
pass
7+
8+
9+
def __lldb_init_module(debugger, dict):
10+
if not "SKIP_SCRIPTED_PROCESS_LAUNCH" in os.environ:
11+
debugger.HandleCommand(
12+
"process launch -C %s.%s"
13+
% (__name__, MissingMethodsScriptedProcess.__name__)
14+
)
15+
else:
16+
print(
17+
"Name of the class that will manage the scripted process: '%s.%s'"
18+
% (__name__, MissingMethodsScriptedProcess.__name__)
19+
)

lldb/unittests/ScriptInterpreter/Python/PythonDataObjectsTests.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,9 @@ TEST_F(PythonDataObjectsTest, TestPythonDictionaryManipulation) {
504504
dict.SetItemForKey(keys[i], values[i]);
505505

506506
EXPECT_EQ(dict_entries, dict.GetSize());
507+
EXPECT_FALSE(dict.HasKey("not_in_dict"));
508+
EXPECT_TRUE(dict.HasKey(key_0));
509+
EXPECT_TRUE(dict.HasKey(key_1));
507510

508511
// Verify that the keys and values match
509512
PythonObject chk_value1 = dict.GetItemForKey(keys[0]);

0 commit comments

Comments
 (0)