[Lldb-commits] [lldb] [lldb] Add `scripting extension instances` command (PR #226043)
Med Ismail Bennani via lldb-commits
lldb-commits at lists.llvm.org
Fri Sep 25 16:53:46 PDT 2026
https://github.com/medismailben updated https://github.com/llvm/llvm-project/pull/226043
>From 4ca8acce50a3b2bcfbc99dd60a61977d0d7c9af1 Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Fri, 25 Sep 2026 16:37:48 -0700
Subject: [PATCH 1/2] [lldb] Add label, title and divider ANSI settings
Commands that print structured listings hard-coded their colors, but
terminal themes render ANSI colors differently, so users couldn't adjust
them the way they can with the prompt, progress or autosuggestion colors.
Add `label-ansi-prefix`, `title-ansi-prefix` and `divider-ansi-prefix`
settings, each with a matching suffix, for the field labels, entry
titles and dividers such listings are made of. Their defaults are the
colors `scripting extension list` uses today, and it now reads them
from these settings.
Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
lldb/include/lldb/Core/Debugger.h | 12 +++++++
.../Commands/CommandObjectScripting.cpp | 34 +++++++++++-------
lldb/source/Core/CoreProperties.td | 24 +++++++++++++
lldb/source/Core/Debugger.cpp | 36 +++++++++++++++++++
4 files changed, 94 insertions(+), 12 deletions(-)
diff --git a/lldb/include/lldb/Core/Debugger.h b/lldb/include/lldb/Core/Debugger.h
index 2501eeabfe506d..d3352504497e43 100644
--- a/lldb/include/lldb/Core/Debugger.h
+++ b/lldb/include/lldb/Core/Debugger.h
@@ -344,6 +344,18 @@ class Debugger : public std::enable_shared_from_this<Debugger>,
llvm::StringRef GetShowProgressAnsiSuffix() const;
+ llvm::StringRef GetLabelAnsiPrefix() const;
+
+ llvm::StringRef GetLabelAnsiSuffix() const;
+
+ llvm::StringRef GetTitleAnsiPrefix() const;
+
+ llvm::StringRef GetTitleAnsiSuffix() const;
+
+ llvm::StringRef GetDividerAnsiPrefix() const;
+
+ llvm::StringRef GetDividerAnsiSuffix() const;
+
llvm::StringRef GetDisabledAnsiPrefix() const;
llvm::StringRef GetDisabledAnsiSuffix() const;
diff --git a/lldb/source/Commands/CommandObjectScripting.cpp b/lldb/source/Commands/CommandObjectScripting.cpp
index 02c19ed4ef0cd2..636d092bacd89e 100644
--- a/lldb/source/Commands/CommandObjectScripting.cpp
+++ b/lldb/source/Commands/CommandObjectScripting.cpp
@@ -245,6 +245,16 @@ class CommandObjectScriptingExtensionList : public CommandObjectParsed {
}
private:
+ std::string GetSeparator(bool use_color) {
+ const Debugger &debugger = GetDebugger();
+ return ansi::FormatAnsiTerminalCodes(debugger.GetDividerAnsiPrefix(),
+ use_color) +
+ std::string(std::min<uint64_t>(debugger.GetTerminalWidth(), 80),
+ '-') +
+ ansi::FormatAnsiTerminalCodes(debugger.GetDividerAnsiSuffix(),
+ use_color);
+ }
+
std::vector<std::string>
GetLanguagesForExtension(const std::vector<size_t> &indices) {
std::vector<std::string> languages;
@@ -310,27 +320,27 @@ class CommandObjectScriptingExtensionList : public CommandObjectParsed {
CommandReturnObject &result) {
Stream &s = result.GetOutputStream();
const bool use_color = s.AsRawOstream().colors_enabled();
+ const Debugger &debugger = GetDebugger();
auto ansi_code = [use_color](llvm::StringRef code) {
return ansi::FormatAnsiTerminalCodes(code, use_color);
};
- const std::string label_color = ansi_code("${ansi.fg.green}${ansi.bold}");
- const std::string name_color = ansi_code("${ansi.fg.cyan}${ansi.bold}");
- const std::string sep_color = ansi_code("${ansi.faint}");
- const std::string reset = ansi_code("${ansi.normal}");
- const std::string separator(
- std::min<uint64_t>(GetDebugger().GetTerminalWidth(), 80), '-');
+ const std::string label_prefix = ansi_code(debugger.GetLabelAnsiPrefix());
+ const std::string label_suffix = ansi_code(debugger.GetLabelAnsiSuffix());
+ const std::string title_prefix = ansi_code(debugger.GetTitleAnsiPrefix());
+ const std::string title_suffix = ansi_code(debugger.GetTitleAnsiSuffix());
+ const std::string separator = GetSeparator(use_color);
s.PutCString("Available scripted extension templates:");
auto print_field = [&](llvm::StringRef key, llvm::StringRef value,
- const std::string &value_color = std::string()) {
+ bool is_title = false) {
if (value.empty())
return;
s.IndentMore();
s.Indent();
- s << label_color << key << ": " << reset;
- if (!value_color.empty())
- s << value_color << value << reset;
+ s << label_prefix << key << ": " << label_suffix;
+ if (is_title)
+ s << title_prefix << value << title_suffix;
else
s << value;
s << '\n';
@@ -346,7 +356,7 @@ class CommandObjectScriptingExtensionList : public CommandObjectParsed {
num_listed_interface++;
s.EOL();
- s << sep_color << separator << reset;
+ s << separator;
s.EOL();
llvm::StringRef desc =
@@ -356,7 +366,7 @@ class CommandObjectScriptingExtensionList : public CommandObjectParsed {
PluginManager::GetScriptedInterfaceUsagesAtIndex(
extension_pair.second[0]);
- print_field("Name", extension_pair.first(), name_color);
+ print_field("Name", extension_pair.first(), /*is_title=*/true);
print_field("Description", desc);
print_field("Language", llvm::join(languages, ""));
usages.Dump(s, ScriptedInterfaceUsages::UsageKind::API, use_color);
diff --git a/lldb/source/Core/CoreProperties.td b/lldb/source/Core/CoreProperties.td
index dee4cd40448bfc..268d5da19fadda 100644
--- a/lldb/source/Core/CoreProperties.td
+++ b/lldb/source/Core/CoreProperties.td
@@ -206,6 +206,30 @@ let Definition = "debugger", Path = "" in {
Global,
DefaultStringValue<"${ansi.normal}">,
Desc<"When displaying progress in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the progress message.">;
+ def LabelAnsiPrefix: Property<"label-ansi-prefix", "String">,
+ Global,
+ DefaultStringValue<"${ansi.fg.green}${ansi.bold}">,
+ Desc<"When displaying a field label in command output in a color-enabled terminal, use the ANSI terminal code specified in this format immediately before the label.">;
+ def LabelAnsiSuffix: Property<"label-ansi-suffix", "String">,
+ Global,
+ DefaultStringValue<"${ansi.normal}">,
+ Desc<"When displaying a field label in command output in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the label.">;
+ def TitleAnsiPrefix: Property<"title-ansi-prefix", "String">,
+ Global,
+ DefaultStringValue<"${ansi.fg.cyan}${ansi.bold}">,
+ Desc<"When displaying the title of an entry in a list in a color-enabled terminal, use the ANSI terminal code specified in this format immediately before the title.">;
+ def TitleAnsiSuffix: Property<"title-ansi-suffix", "String">,
+ Global,
+ DefaultStringValue<"${ansi.normal}">,
+ Desc<"When displaying the title of an entry in a list in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the title.">;
+ def DividerAnsiPrefix: Property<"divider-ansi-prefix", "String">,
+ Global,
+ DefaultStringValue<"${ansi.faint}">,
+ Desc<"When displaying the divider between entries of a list in a color-enabled terminal, use the ANSI terminal code specified in this format immediately before the divider.">;
+ def DividerAnsiSuffix: Property<"divider-ansi-suffix", "String">,
+ Global,
+ DefaultStringValue<"${ansi.normal}">,
+ Desc<"When displaying the divider between entries of a list in a color-enabled terminal, use the ANSI terminal code specified in this format immediately after the divider.">;
def ShowStatusline: Property<"show-statusline", "Boolean">,
Global,
DefaultTrue,
diff --git a/lldb/source/Core/Debugger.cpp b/lldb/source/Core/Debugger.cpp
index 231295d0b523d8..db55a0d416b279 100644
--- a/lldb/source/Core/Debugger.cpp
+++ b/lldb/source/Core/Debugger.cpp
@@ -578,6 +578,42 @@ llvm::StringRef Debugger::GetShowProgressAnsiSuffix() const {
idx, g_debugger_properties[idx].default_cstr_value);
}
+llvm::StringRef Debugger::GetLabelAnsiPrefix() const {
+ const uint32_t idx = ePropertyLabelAnsiPrefix;
+ return GetPropertyAtIndexAs<llvm::StringRef>(
+ idx, g_debugger_properties[idx].default_cstr_value);
+}
+
+llvm::StringRef Debugger::GetLabelAnsiSuffix() const {
+ const uint32_t idx = ePropertyLabelAnsiSuffix;
+ return GetPropertyAtIndexAs<llvm::StringRef>(
+ idx, g_debugger_properties[idx].default_cstr_value);
+}
+
+llvm::StringRef Debugger::GetTitleAnsiPrefix() const {
+ const uint32_t idx = ePropertyTitleAnsiPrefix;
+ return GetPropertyAtIndexAs<llvm::StringRef>(
+ idx, g_debugger_properties[idx].default_cstr_value);
+}
+
+llvm::StringRef Debugger::GetTitleAnsiSuffix() const {
+ const uint32_t idx = ePropertyTitleAnsiSuffix;
+ return GetPropertyAtIndexAs<llvm::StringRef>(
+ idx, g_debugger_properties[idx].default_cstr_value);
+}
+
+llvm::StringRef Debugger::GetDividerAnsiPrefix() const {
+ const uint32_t idx = ePropertyDividerAnsiPrefix;
+ return GetPropertyAtIndexAs<llvm::StringRef>(
+ idx, g_debugger_properties[idx].default_cstr_value);
+}
+
+llvm::StringRef Debugger::GetDividerAnsiSuffix() const {
+ const uint32_t idx = ePropertyDividerAnsiSuffix;
+ return GetPropertyAtIndexAs<llvm::StringRef>(
+ idx, g_debugger_properties[idx].default_cstr_value);
+}
+
bool Debugger::GetShowStatusline() const {
const uint32_t idx = ePropertyShowStatusline;
return GetPropertyAtIndexAs<bool>(
>From 16bbf1aa7581f4325e9edf60af7b7f8d9782561d Mon Sep 17 00:00:00 2001
From: Med Ismail Bennani <ismail at bennani.ma>
Date: Fri, 25 Sep 2026 16:31:54 -0700
Subject: [PATCH 2/2] [lldb] Add `scripting extension list --instances`
There is currently no way to tell which scripted extensions are alive
in a debug session, or which script file a given extension class came
from.
This patch addresses that by recording the file of every module imported
by the script interpreter, and attributing it to the ScriptedMetadata of
each extension object created from one of its classes. It tracks every
successfully created extension object in a registry owned by the script
interpreter, and lists them with the new `--instances` flag of
`scripting extension list`, grouped by class. Each instance shows a
UUID, the address of its script object and the arguments it was created
with:
```
(lldb) scripting extension list --instances
Instantiated scripted extensions:
--------------------------------------------------------------------------------
Class: resolver.Resolver
Extension: ScriptedBreakpointResolver
Path: /tmp/resolver.py
Instance: 430F093E-89C6-EEF1-9602-E951A3A523DF (0x90000759e54f1a0)
Args:
- symbol: break_on_me
--------------------------------------------------------------------------------
Class: stop_hook.stop_handler
Extension: ScriptedHook
Path: /tmp/stop_hook.py
Instances:
[0] 7576EB2F-4E4D-1931-D006-F632A51696BD (0x50000784d2584a0)
Args:
- increment: 5
[1] C95B90A2-E199-343F-B7F7-AC2E360BFF2F (0x50000759e54dfa0)
Args:
- increment: 1
- return_false: 1
```
Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
---
.../Interfaces/ScriptedInterface.h | 40 +++-
.../lldb/Interpreter/ScriptInterpreter.h | 19 ++
.../Interpreter/ScriptedInstanceRegistry.h | 100 ++++++++++
lldb/include/lldb/Utility/ScriptedMetadata.h | 8 +
lldb/include/lldb/lldb-forward.h | 5 +
.../Commands/CommandObjectScripting.cpp | 61 ++++++-
lldb/source/Commands/Options.td | 4 +
lldb/source/Interpreter/CMakeLists.txt | 1 +
lldb/source/Interpreter/ScriptInterpreter.cpp | 20 ++
.../Interpreter/ScriptedInstanceRegistry.cpp | 152 ++++++++++++++++
.../Interfaces/ScriptedPythonInterface.h | 29 +++
.../Python/ScriptInterpreterPython.cpp | 16 ++
.../TestScriptingExtensionInstances.py | 171 ++++++++++++++++++
.../scripting/extension/instance_cmds.py | 17 ++
.../Interpreter/TestScriptedInterface.cpp | 49 +++++
15 files changed, 689 insertions(+), 3 deletions(-)
create mode 100644 lldb/include/lldb/Interpreter/ScriptedInstanceRegistry.h
create mode 100644 lldb/source/Interpreter/ScriptedInstanceRegistry.cpp
create mode 100644 lldb/test/API/commands/scripting/extension/TestScriptingExtensionInstances.py
create mode 100644 lldb/test/API/commands/scripting/extension/instance_cmds.py
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedInterface.h
index 79c2f08a1820b2..9c537520fb2a53 100644
--- a/lldb/include/lldb/Interpreter/Interfaces/ScriptedInterface.h
+++ b/lldb/include/lldb/Interpreter/Interfaces/ScriptedInterface.h
@@ -10,6 +10,7 @@
#define LLDB_INTERPRETER_INTERFACES_SCRIPTEDINTERFACE_H
#include "ScriptedInterfaceUsages.h"
+#include "lldb/Interpreter/ScriptedInstanceRegistry.h"
#include "lldb/Core/StructuredDataImpl.h"
#include "lldb/Utility/LLDBLog.h"
@@ -27,7 +28,12 @@ namespace lldb_private {
class ScriptedInterface {
public:
ScriptedInterface() = default;
- virtual ~ScriptedInterface() = default;
+ virtual ~ScriptedInterface() { UnregisterInstance(); }
+
+ // Copies would share the registry entry, and the first one destroyed would
+ // drop it while the other is still alive.
+ ScriptedInterface(const ScriptedInterface &) = delete;
+ ScriptedInterface &operator=(const ScriptedInterface &) = delete;
StructuredData::GenericSP GetScriptObjectInstance() {
return m_object_instance_sp;
@@ -37,6 +43,8 @@ class ScriptedInterface {
return m_scripted_metadata;
}
+ virtual llvm::StringRef GetPluginName() = 0;
+
/// Whether the user can invoke this extension directly, the way a scripted
/// command can. Those never introduce the target's API mutex bypass, so at
/// top level they serialize like any other command; nested inside an
@@ -101,8 +109,38 @@ class ScriptedInterface {
}
protected:
+ void RegisterInstance(const lldb::ScriptedInstanceRegistrySP ®istry_sp,
+ llvm::StringRef class_name) {
+ UnregisterInstance();
+ if (!registry_sp)
+ return;
+ ScriptedInstanceInfo info;
+ info.plugin_name = GetPluginName();
+ info.class_name = class_name.str();
+ if (m_object_instance_sp)
+ info.object_address =
+ reinterpret_cast<uintptr_t>(m_object_instance_sp->GetValue());
+ if (m_scripted_metadata) {
+ info.source_path = m_scripted_metadata->GetSourcePath();
+ info.args_sp = m_scripted_metadata->GetArgsSP();
+ }
+ m_registry_id = registry_sp->Add(std::move(info));
+ m_registry_wp = registry_sp;
+ }
+
StructuredData::GenericSP m_object_instance_sp;
std::optional<ScriptedMetadata> m_scripted_metadata;
+
+private:
+ void UnregisterInstance() {
+ // The interpreter owning the registry may already be gone.
+ if (auto registry_sp = m_registry_wp.lock())
+ registry_sp->Remove(m_registry_id);
+ m_registry_wp.reset();
+ }
+
+ lldb::ScriptedInstanceRegistryWP m_registry_wp;
+ uint64_t m_registry_id = 0;
};
} // namespace lldb_private
diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
index 7ad530b3233f2f..eb25337e58194a 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -22,11 +22,15 @@
#include "lldb/Interpreter/Interfaces/ScriptedProcessInterface.h"
#include "lldb/Interpreter/Interfaces/ScriptedThreadInterface.h"
#include "lldb/Interpreter/ScriptObject.h"
+#include "lldb/Interpreter/ScriptedInstanceRegistry.h"
#include "lldb/Utility/Broadcaster.h"
#include "lldb/Utility/Status.h"
#include "lldb/Utility/StructuredData.h"
#include "lldb/Utility/UnimplementedError.h"
#include "lldb/lldb-private.h"
+#include "llvm/ADT/StringMap.h"
+#include <memory>
+#include <mutex>
#include <optional>
namespace lldb_private {
@@ -379,6 +383,15 @@ class ScriptInterpreter : public PluginInterface {
FileSpec extra_search_dir = {},
lldb::TargetSP loaded_into_target_sp = {});
+ void SetImportedModulePath(llvm::StringRef module_name, const FileSpec &path);
+
+ /// Falls back to the enclosing package for submodules of an imported one.
+ FileSpec GetImportedModulePath(llvm::StringRef module_name) const;
+
+ const lldb::ScriptedInstanceRegistrySP &GetScriptedInstanceRegistry() const {
+ return m_instance_registry_sp;
+ }
+
virtual bool IsReservedWord(const char *word) { return false; }
virtual std::unique_ptr<ScriptInterpreterLocker> AcquireInterpreterLock();
@@ -522,6 +535,12 @@ class ScriptInterpreter : public PluginInterface {
protected:
Debugger &m_debugger;
lldb::ScriptLanguage m_script_lang;
+
+private:
+ mutable std::mutex m_imported_modules_mutex;
+ llvm::StringMap<FileSpec> m_imported_modules;
+ lldb::ScriptedInstanceRegistrySP m_instance_registry_sp =
+ std::make_shared<ScriptedInstanceRegistry>();
};
} // namespace lldb_private
diff --git a/lldb/include/lldb/Interpreter/ScriptedInstanceRegistry.h b/lldb/include/lldb/Interpreter/ScriptedInstanceRegistry.h
new file mode 100644
index 00000000000000..817d4142811121
--- /dev/null
+++ b/lldb/include/lldb/Interpreter/ScriptedInstanceRegistry.h
@@ -0,0 +1,100 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_INTERPRETER_SCRIPTEDINSTANCEREGISTRY_H
+#define LLDB_INTERPRETER_SCRIPTEDINSTANCEREGISTRY_H
+
+#include "lldb/Utility/FileSpec.h"
+#include "lldb/Utility/StructuredData.h"
+#include "lldb/Utility/UUID.h"
+#include "lldb/lldb-defines.h"
+#include "lldb/lldb-enumerations.h"
+#include "lldb/lldb-forward.h"
+#include "lldb/lldb-types.h"
+#include "llvm/ADT/ArrayRef.h"
+#include "llvm/ADT/DenseMap.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/StringRef.h"
+
+#include <cstdint>
+#include <mutex>
+#include <string>
+#include <vector>
+
+namespace lldb_private {
+
+struct ScriptedInstanceInfo {
+ uint64_t id = 0;
+ UUID uuid;
+ lldb::addr_t object_address = LLDB_INVALID_ADDRESS;
+ /// Plugin names are string literals, so this never dangles.
+ llvm::StringRef plugin_name;
+ std::string class_name;
+ FileSpec source_path;
+ StructuredData::DictionarySP args_sp;
+};
+
+struct ScriptedInstanceGroup {
+ std::string class_name;
+ lldb::ScriptedExtension extension = lldb::eScriptedExtensionInvalid;
+ FileSpec source_path;
+ std::vector<ScriptedInstanceInfo> instances;
+
+ StructuredData::DictionarySP ToStructuredData() const;
+
+ void Dump(Stream &s, const Debugger &debugger, bool use_color) const;
+};
+
+/// Entries are snapshots taken when the object is created, so listing them
+/// never calls back into an interface that might be mid-destruction on
+/// another thread.
+class ScriptedInstanceRegistry {
+public:
+ uint64_t Add(ScriptedInstanceInfo info) {
+ std::lock_guard<std::mutex> guard(m_mutex);
+ const uint64_t id = m_next_id++;
+ info.id = id;
+ info.uuid = UUID::Generate();
+ m_instances[id] = std::move(info);
+ return id;
+ }
+
+ void Remove(uint64_t id) {
+ std::lock_guard<std::mutex> guard(m_mutex);
+ m_instances.erase(id);
+ }
+
+ std::vector<ScriptedInstanceInfo> GetInstances() const {
+ std::vector<ScriptedInstanceInfo> instances = CopyInstances();
+ llvm::sort(instances,
+ [](const ScriptedInstanceInfo &lhs,
+ const ScriptedInstanceInfo &rhs) { return lhs.id < rhs.id; });
+ return instances;
+ }
+
+ std::vector<ScriptedInstanceGroup> GetInstanceGroups(
+ llvm::ArrayRef<lldb::ScriptedExtension> extensions = {}) const;
+
+private:
+ std::vector<ScriptedInstanceInfo> CopyInstances() const {
+ std::lock_guard<std::mutex> guard(m_mutex);
+ std::vector<ScriptedInstanceInfo> instances;
+ instances.reserve(m_instances.size());
+ for (const auto &entry : m_instances)
+ instances.push_back(entry.second);
+ return instances;
+ }
+
+ mutable std::mutex m_mutex;
+ uint64_t m_next_id = 1;
+ llvm::DenseMap<uint64_t, ScriptedInstanceInfo> m_instances;
+};
+
+} // namespace lldb_private
+
+#endif // LLDB_INTERPRETER_SCRIPTEDINSTANCEREGISTRY_H
diff --git a/lldb/include/lldb/Utility/ScriptedMetadata.h b/lldb/include/lldb/Utility/ScriptedMetadata.h
index fcbcc70bc36e36..38dad813070c58 100644
--- a/lldb/include/lldb/Utility/ScriptedMetadata.h
+++ b/lldb/include/lldb/Utility/ScriptedMetadata.h
@@ -9,6 +9,7 @@
#ifndef LLDB_UTILITY_SCRIPTEDMETADATA_H
#define LLDB_UTILITY_SCRIPTEDMETADATA_H
+#include "lldb/Utility/FileSpec.h"
#include "lldb/Utility/ProcessInfo.h"
#include "lldb/Utility/StreamString.h"
#include "lldb/Utility/StructuredData.h"
@@ -26,6 +27,7 @@ class ScriptedMetadata {
if (metadata_sp) {
m_class_name = metadata_sp->GetClassName();
m_args_sp = metadata_sp->GetArgsSP();
+ m_source_path = metadata_sp->GetSourcePath();
}
}
@@ -37,6 +39,11 @@ class ScriptedMetadata {
llvm::StringRef GetClassName() const { return m_class_name; }
StructuredData::DictionarySP GetArgsSP() const { return m_args_sp; }
+ const FileSpec &GetSourcePath() const { return m_source_path; }
+ void SetSourcePath(const FileSpec &source_path) {
+ m_source_path = source_path;
+ }
+
/// Get a unique identifier for this metadata based on its contents.
/// The ID is computed from the class name and arguments dictionary,
/// not from the pointer address, so two metadata objects with the same
@@ -62,6 +69,7 @@ class ScriptedMetadata {
private:
std::string m_class_name;
StructuredData::DictionarySP m_args_sp;
+ FileSpec m_source_path;
};
} // namespace lldb_private
diff --git a/lldb/include/lldb/lldb-forward.h b/lldb/include/lldb/lldb-forward.h
index f48f077e9b5ee7..649f7b704d68ea 100644
--- a/lldb/include/lldb/lldb-forward.h
+++ b/lldb/include/lldb/lldb-forward.h
@@ -197,6 +197,7 @@ class ScriptedMetadata;
class ScriptedBreakpointInterface;
class ScriptedCommandInterface;
class ScriptedHookInterface;
+class ScriptedInstanceRegistry;
class ScriptedPlatformInterface;
class ScriptedProcessInterface;
class ScriptedThreadInterface;
@@ -436,6 +437,10 @@ typedef std::unique_ptr<lldb_private::ScriptedProcessInterface>
ScriptedProcessInterfaceUP;
typedef std::shared_ptr<lldb_private::ScriptedHookInterface>
ScriptedHookInterfaceSP;
+typedef std::shared_ptr<lldb_private::ScriptedInstanceRegistry>
+ ScriptedInstanceRegistrySP;
+typedef std::weak_ptr<lldb_private::ScriptedInstanceRegistry>
+ ScriptedInstanceRegistryWP;
typedef std::shared_ptr<lldb_private::ScriptedThreadInterface>
ScriptedThreadInterfaceSP;
typedef std::shared_ptr<lldb_private::ScriptedThreadPlanInterface>
diff --git a/lldb/source/Commands/CommandObjectScripting.cpp b/lldb/source/Commands/CommandObjectScripting.cpp
index 636d092bacd89e..3eef07384200af 100644
--- a/lldb/source/Commands/CommandObjectScripting.cpp
+++ b/lldb/source/Commands/CommandObjectScripting.cpp
@@ -143,9 +143,11 @@ class CommandObjectScriptingExtensionList : public CommandObjectParsed {
CommandObjectScriptingExtensionList(CommandInterpreter &interpreter)
: CommandObjectParsed(
interpreter, "scripting extension list",
- "List all the available scripting extension templates. ",
+ "List the available scripting extension templates, or with "
+ "--instances, the instantiated scripting extensions grouped by "
+ "class.",
"scripting extension list [--language <scripting-language> --] "
- "[--json --] [<extension-name> ...]") {
+ "[--json --] [--instances --] [<extension-name> ...]") {
AddSimpleArgumentList(eArgTypeScriptedExtension, eArgRepeatStar);
}
@@ -182,6 +184,9 @@ class CommandObjectScriptingExtensionList : public CommandObjectParsed {
case 'j':
m_json_format = true;
break;
+ case 'i':
+ m_instances = true;
+ break;
default:
llvm_unreachable("Unimplemented option");
}
@@ -192,6 +197,7 @@ class CommandObjectScriptingExtensionList : public CommandObjectParsed {
void OptionParsingStarting(ExecutionContext *execution_context) override {
m_language = lldb::eScriptLanguageDefault;
m_json_format = false;
+ m_instances = false;
}
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
@@ -200,10 +206,16 @@ class CommandObjectScriptingExtensionList : public CommandObjectParsed {
lldb::ScriptLanguage m_language = lldb::eScriptLanguageDefault;
bool m_json_format = false;
+ bool m_instances = false;
};
protected:
void DoExecute(Args &command, CommandReturnObject &result) override {
+ if (m_options.m_instances) {
+ ListInstances(command, result);
+ return;
+ }
+
llvm::StringMap<std::vector<size_t>> grouped_by_extension;
for (size_t i = 0; i < PluginManager::GetNumScriptedInterfaces(); i++) {
lldb::ScriptedExtension extension =
@@ -255,6 +267,51 @@ class CommandObjectScriptingExtensionList : public CommandObjectParsed {
use_color);
}
+ void ListInstances(Args &command, CommandReturnObject &result) {
+ std::vector<lldb::ScriptedExtension> extensions;
+ for (const Args::ArgEntry &arg : command.entries()) {
+ lldb::ScriptedExtension extension =
+ ScriptInterpreter::StringToExtension(arg.ref());
+ if (extension == eScriptedExtensionInvalid) {
+ result.AppendErrorWithFormat("no scripted extension named '%s'",
+ arg.c_str());
+ return;
+ }
+ extensions.push_back(extension);
+ }
+
+ std::vector<ScriptedInstanceGroup> groups;
+ if (ScriptInterpreter *script_interpreter =
+ GetDebugger().GetScriptInterpreter(/*can_create=*/false))
+ groups =
+ script_interpreter->GetScriptedInstanceRegistry()->GetInstanceGroups(
+ extensions);
+
+ if (m_options.m_json_format) {
+ StructuredData::Array groups_array;
+ for (const ScriptedInstanceGroup &group : groups)
+ groups_array.AddItem(group.ToStructuredData());
+ groups_array.Dump(result.GetOutputStream());
+ result.GetOutputStream().EOL();
+ result.SetStatus(eReturnStatusSuccessFinishResult);
+ return;
+ }
+
+ Stream &s = result.GetOutputStream();
+ const bool use_color = s.AsRawOstream().colors_enabled();
+ const std::string separator = GetSeparator(use_color);
+
+ s.PutCString("Instantiated scripted extensions:");
+ if (groups.empty())
+ s << " None";
+ s.EOL();
+ for (const ScriptedInstanceGroup &group : groups) {
+ s << separator << '\n';
+ group.Dump(s, GetDebugger(), use_color);
+ }
+ result.SetStatus(eReturnStatusSuccessFinishResult);
+ }
+
std::vector<std::string>
GetLanguagesForExtension(const std::vector<size_t> &indices) {
std::vector<std::string> languages;
diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td
index ab851725979ef4..86514999d755d4 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1585,6 +1585,10 @@ let Command = "scripting extension list" in {
def scripting_extension_list_json
: Option<"json", "j">,
Desc<"Output the scripted extension list in json format.">;
+ def scripting_extension_list_instances
+ : Option<"instances", "i">,
+ Desc<"List the instantiated scripted extensions, grouped by class, "
+ "instead of the available templates.">;
}
let Command = "scripting extension generate" in {
diff --git a/lldb/source/Interpreter/CMakeLists.txt b/lldb/source/Interpreter/CMakeLists.txt
index 8a9605b568865b..17290df85e2ecf 100644
--- a/lldb/source/Interpreter/CMakeLists.txt
+++ b/lldb/source/Interpreter/CMakeLists.txt
@@ -58,6 +58,7 @@ add_lldb_library(lldbInterpreter NO_PLUGIN_DEPENDENCIES
Options.cpp
Property.cpp
ScriptInterpreter.cpp
+ ScriptedInstanceRegistry.cpp
ADDITIONAL_HEADER_DIRS
${LLDB_INCLUDE_DIR}/lldb/Interpreter
diff --git a/lldb/source/Interpreter/ScriptInterpreter.cpp b/lldb/source/Interpreter/ScriptInterpreter.cpp
index a53f3b744f02ee..0e402381b3a4ee 100644
--- a/lldb/source/Interpreter/ScriptInterpreter.cpp
+++ b/lldb/source/Interpreter/ScriptInterpreter.cpp
@@ -65,6 +65,26 @@ bool ScriptInterpreter::LoadScriptingModule(
return false;
}
+void ScriptInterpreter::SetImportedModulePath(llvm::StringRef module_name,
+ const FileSpec &path) {
+ std::lock_guard<std::mutex> guard(m_imported_modules_mutex);
+ m_imported_modules[module_name] = path;
+}
+
+FileSpec
+ScriptInterpreter::GetImportedModulePath(llvm::StringRef module_name) const {
+ std::lock_guard<std::mutex> guard(m_imported_modules_mutex);
+ while (true) {
+ auto it = m_imported_modules.find(module_name);
+ if (it != m_imported_modules.end())
+ return it->second;
+ size_t dot_pos = module_name.rfind('.');
+ if (dot_pos == llvm::StringRef::npos)
+ return {};
+ module_name = module_name.take_front(dot_pos);
+ }
+}
+
std::string ScriptInterpreter::LanguageToString(lldb::ScriptLanguage language) {
switch (language) {
case eScriptLanguageNone:
diff --git a/lldb/source/Interpreter/ScriptedInstanceRegistry.cpp b/lldb/source/Interpreter/ScriptedInstanceRegistry.cpp
new file mode 100644
index 00000000000000..d2b260b455f8f3
--- /dev/null
+++ b/lldb/source/Interpreter/ScriptedInstanceRegistry.cpp
@@ -0,0 +1,152 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#include "lldb/Interpreter/ScriptedInstanceRegistry.h"
+#include "lldb/Core/Debugger.h"
+#include "lldb/Core/PluginManager.h"
+#include "lldb/Interpreter/ScriptInterpreter.h"
+#include "lldb/Utility/AnsiTerminal.h"
+#include "lldb/Utility/Stream.h"
+
+#include "llvm/ADT/StringMap.h"
+
+#include <tuple>
+
+using namespace lldb;
+using namespace lldb_private;
+
+static bool HasArgs(const ScriptedInstanceInfo &info) {
+ return info.args_sp && info.args_sp->GetSize();
+}
+
+std::vector<ScriptedInstanceGroup> ScriptedInstanceRegistry::GetInstanceGroups(
+ llvm::ArrayRef<ScriptedExtension> extensions) const {
+ // Unknown names map to eScriptedExtensionInvalid, the zero value.
+ llvm::StringMap<ScriptedExtension> extension_by_plugin;
+ for (uint32_t i = 0; i < PluginManager::GetNumScriptedInterfaces(); i++)
+ extension_by_plugin[PluginManager::GetScriptedInterfaceNameAtIndex(i)] =
+ PluginManager::GetScriptedInterfaceExtensionAtIndex(i);
+
+ std::vector<ScriptedInstanceGroup> groups;
+ for (ScriptedInstanceInfo &info : CopyInstances()) {
+ ScriptedExtension extension = extension_by_plugin.lookup(info.plugin_name);
+ if (!extensions.empty() && !llvm::is_contained(extensions, extension))
+ continue;
+
+ auto it = llvm::find_if(groups, [&](const ScriptedInstanceGroup &group) {
+ return group.class_name == info.class_name &&
+ group.source_path == info.source_path &&
+ group.extension == extension;
+ });
+ if (it == groups.end()) {
+ it = groups.insert(groups.end(), ScriptedInstanceGroup());
+ it->class_name = info.class_name;
+ it->extension = extension;
+ it->source_path = info.source_path;
+ }
+ it->instances.push_back(std::move(info));
+ }
+
+ for (ScriptedInstanceGroup &group : groups)
+ llvm::sort(group.instances,
+ [](const ScriptedInstanceInfo &lhs,
+ const ScriptedInstanceInfo &rhs) { return lhs.id < rhs.id; });
+ llvm::sort(groups, [](const ScriptedInstanceGroup &lhs,
+ const ScriptedInstanceGroup &rhs) {
+ return std::make_tuple(llvm::StringRef(lhs.class_name),
+ lhs.source_path.GetPath(), lhs.extension) <
+ std::make_tuple(llvm::StringRef(rhs.class_name),
+ rhs.source_path.GetPath(), rhs.extension);
+ });
+ return groups;
+}
+
+StructuredData::DictionarySP ScriptedInstanceGroup::ToStructuredData() const {
+ auto group_sp = std::make_shared<StructuredData::Dictionary>();
+ group_sp->AddStringItem("class_name", class_name);
+ group_sp->AddStringItem("extension",
+ ScriptInterpreter::ExtensionToString(extension));
+ if (source_path)
+ group_sp->AddStringItem("source_path", source_path.GetPath());
+
+ auto instances_sp = std::make_shared<StructuredData::Array>();
+ for (const ScriptedInstanceInfo &info : instances) {
+ auto instance_sp = std::make_shared<StructuredData::Dictionary>();
+ instance_sp->AddStringItem("uuid", info.uuid.GetAsString());
+ if (info.object_address != LLDB_INVALID_ADDRESS)
+ instance_sp->AddIntegerItem("address", info.object_address);
+ if (HasArgs(info))
+ instance_sp->AddItem("args", info.args_sp);
+ instances_sp->AddItem(instance_sp);
+ }
+ group_sp->AddItem("instances", instances_sp);
+ return group_sp;
+}
+
+void ScriptedInstanceGroup::Dump(Stream &s, const Debugger &debugger,
+ bool use_color) const {
+ auto ansi_code = [use_color](llvm::StringRef code) {
+ return ansi::FormatAnsiTerminalCodes(code, use_color);
+ };
+ const std::string label_prefix = ansi_code(debugger.GetLabelAnsiPrefix());
+ const std::string label_suffix = ansi_code(debugger.GetLabelAnsiSuffix());
+ const std::string title_prefix = ansi_code(debugger.GetTitleAnsiPrefix());
+ const std::string title_suffix = ansi_code(debugger.GetTitleAnsiSuffix());
+
+ auto print_label = [&](llvm::StringRef indent, llvm::StringRef label) {
+ s << indent << label_prefix << label << ':' << label_suffix;
+ };
+ auto print_instance = [&](const ScriptedInstanceInfo &info) {
+ s << info.uuid.GetAsString();
+ if (info.object_address != LLDB_INVALID_ADDRESS)
+ s.Format(" ({0:x})", info.object_address);
+ s << '\n';
+ };
+ auto print_args = [&](const ScriptedInstanceInfo &info,
+ llvm::StringRef indent) {
+ if (!HasArgs(info))
+ return;
+ print_label(indent, "Args");
+ s << '\n';
+ info.args_sp->ForEachSorted(
+ [&](llvm::StringRef key, StructuredData::Object *value) {
+ s << indent << " - " << key << ": ";
+ if (StructuredData::String *str = value->GetAsString())
+ s << str->GetValue();
+ else
+ value->Dump(s, /*pretty_print=*/false);
+ s << '\n';
+ return true;
+ });
+ };
+
+ print_label(" ", "Class");
+ s << ' ' << title_prefix << class_name << title_suffix << '\n';
+ print_label(" ", "Extension");
+ s << ' ' << ScriptInterpreter::ExtensionToString(extension) << '\n';
+ if (source_path) {
+ print_label(" ", "Path");
+ s << ' ' << source_path.GetPath() << '\n';
+ }
+
+ if (instances.size() == 1) {
+ print_label(" ", "Instance");
+ s << ' ';
+ print_instance(instances.front());
+ print_args(instances.front(), " ");
+ return;
+ }
+
+ print_label(" ", "Instances");
+ s << '\n';
+ for (auto [idx, info] : llvm::enumerate(instances)) {
+ s.Format(" [{0}] ", idx);
+ print_instance(info);
+ print_args(info, " ");
+ }
+}
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
index eb74e8caea40e7..05c73219904875 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
@@ -416,6 +416,35 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
m_object_instance_sp = StructuredData::GenericSP(
new StructuredPythonObject(std::move(result)));
+
+ std::string qualified_class_name = class_name.str();
+ PythonString obj_module_name =
+ obj_class.GetAttributeValue("__module__").AsType<PythonString>();
+ if (obj_module_name.IsValid()) {
+ if (qualified_class_name.empty())
+ qualified_class_name =
+ llvm::formatv("{0}.{1}", obj_module_name.GetString(),
+ obj_class_name.GetString())
+ .str();
+ if (!m_scripted_metadata->GetSourcePath()) {
+ FileSpec source_path =
+ m_interpreter.GetImportedModulePath(obj_module_name.GetString());
+ // Only LoadScriptingModule records paths, so modules imported any
+ // other way need their __file__.
+ if (!source_path) {
+ if (llvm::Expected<FileSpec> path_or_err = GetScriptedModulePath())
+ source_path = *path_or_err;
+ else
+ LLDB_LOG_ERROR(GetLog(LLDBLog::Script), path_or_err.takeError(),
+ "failed to find the file defining {1}: {0}",
+ qualified_class_name);
+ }
+ m_scripted_metadata->SetSourcePath(source_path);
+ }
+ }
+ RegisterInstance(m_interpreter.GetScriptedInstanceRegistry(),
+ qualified_class_name);
+
return m_object_instance_sp;
}
diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
index bb6d96be97d10b..f81b26dd63adb4 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -2672,6 +2672,22 @@ bool ScriptInterpreterPythonImpl::LoadScriptingModule(
if (error.Fail())
return false;
+ // __lldb_init_module may instantiate extensions from the module, so the
+ // path must be known before it runs.
+ if (llvm::Expected<PythonModule> py_module =
+ PythonModule::Import(module_name)) {
+ PythonObject py_file = py_module->GetAttributeValue("__file__");
+ if (PythonString::Check(py_file.get())) {
+ FileSpec module_path(
+ PythonString(PyRefType::Borrowed, py_file.get()).GetString());
+ FileSystem::Instance().Resolve(module_path);
+ SetImportedModulePath(module_name, module_path);
+ }
+ } else {
+ LLDB_LOG_ERROR(GetLog(LLDBLog::Script), py_module.takeError(),
+ "failed to record the path of module {1}: {0}", module_name);
+ }
+
// if we are here, everything worked
// call __lldb_init_module(debugger,dict)
if (!SWIGBridge::LLDBSwigPythonCallModuleInit(
diff --git a/lldb/test/API/commands/scripting/extension/TestScriptingExtensionInstances.py b/lldb/test/API/commands/scripting/extension/TestScriptingExtensionInstances.py
new file mode 100644
index 00000000000000..061a70cdc5d589
--- /dev/null
+++ b/lldb/test/API/commands/scripting/extension/TestScriptingExtensionInstances.py
@@ -0,0 +1,171 @@
+"""
+Verify that `scripting extension list --instances` reports the live scripted
+extension objects along with the script file their class was imported from.
+"""
+
+import os
+
+import lldb
+from lldbsuite.test.decorators import *
+from lldbsuite.test.lldbtest import *
+
+
+class TestScriptingExtensionInstances(TestBase):
+ NO_DEBUG_INFO_TESTCASE = True
+
+ def list_instances(self, args=""):
+ result = lldb.SBCommandReturnObject()
+ self.dbg.GetCommandInterpreter().HandleCommand(
+ "scripting extension list --instances --json " + args, result
+ )
+ self.assertTrue(result.Succeeded(), result.GetError())
+ data = lldb.SBStructuredData()
+ self.assertSuccess(data.SetFromJSON(result.GetOutput()))
+ self.assertEqual(data.GetType(), lldb.eStructuredDataTypeArray)
+ return [data.GetItemAtIndex(i) for i in range(data.GetSize())]
+
+ def find_group(self, class_name, args=""):
+ for group in self.list_instances(args):
+ if group.GetValueForKey("class_name").GetStringValue(256) == class_name:
+ return group
+ return None
+
+ def get_instances(self, group):
+ instances = group.GetValueForKey("instances")
+ return [instances.GetItemAtIndex(i) for i in range(instances.GetSize())]
+
+ def test_instances(self):
+ script_path = os.path.join(self.getSourceDir(), "instance_cmds.py")
+ self.runCmd("command script import " + script_path)
+ self.runCmd("command script add -c instance_cmds.EchoCommand echo-cmd")
+ self.addTearDownHook(
+ lambda: self.runCmd("command script delete echo-cmd", check=False)
+ )
+
+ group = self.find_group("instance_cmds.EchoCommand")
+ self.assertIsNotNone(group, "scripted command instance is not listed")
+ self.assertEqual(
+ group.GetValueForKey("extension").GetStringValue(256), "ScriptedCommand"
+ )
+ self.assertEqual(
+ os.path.realpath(group.GetValueForKey("source_path").GetStringValue(4096)),
+ os.path.realpath(script_path),
+ )
+ (instance,) = self.get_instances(group)
+ self.assertTrue(instance.GetValueForKey("uuid").GetStringValue(64))
+ self.assertNotEqual(
+ instance.GetValueForKey("address").GetUnsignedIntegerValue(), 0
+ )
+ # Scripted commands take no extra arguments.
+ self.assertFalse(instance.GetValueForKey("args").IsValid())
+
+ self.assertIsNotNone(
+ self.find_group("instance_cmds.EchoCommand", "ScriptedCommand")
+ )
+ self.assertIsNone(
+ self.find_group("instance_cmds.EchoCommand", "ScriptedProcess")
+ )
+
+ self.expect(
+ "scripting extension list --instances",
+ substrs=[
+ "instance_cmds.EchoCommand",
+ "Extension: ScriptedCommand",
+ "Path: " + script_path,
+ "Instance: ",
+ ],
+ )
+
+ self.runCmd("command script delete echo-cmd")
+ self.assertIsNone(self.find_group("instance_cmds.EchoCommand"))
+
+ def test_instances_grouped_by_class(self):
+ script_path = os.path.join(self.getSourceDir(), "instance_cmds.py")
+ self.runCmd("command script import " + script_path)
+ self.assertTrue(self.dbg.CreateTarget(""), VALID_TARGET)
+ self.runCmd("target stop-hook add -P instance_cmds.StopHook -k answer -v 42")
+ self.runCmd("target stop-hook add -P instance_cmds.StopHook -k answer -v 42")
+
+ group = self.find_group("instance_cmds.StopHook", "ScriptedHook")
+ self.assertIsNotNone(group, "stop hook instances are not listed")
+ self.assertEqual(
+ os.path.realpath(group.GetValueForKey("source_path").GetStringValue(4096)),
+ os.path.realpath(script_path),
+ )
+
+ # Identical class and arguments, but still two distinct instances.
+ instances = self.get_instances(group)
+ self.assertEqual(len(instances), 2)
+ uuids = {i.GetValueForKey("uuid").GetStringValue(64) for i in instances}
+ self.assertEqual(len(uuids), 2)
+ # Both objects are alive at once, so they can't share an address.
+ addresses = {
+ i.GetValueForKey("address").GetUnsignedIntegerValue() for i in instances
+ }
+ self.assertEqual(len(addresses), 2)
+ for instance in instances:
+ args = instance.GetValueForKey("args")
+ # Stop hooks store numeric values as integers.
+ self.assertEqual(
+ args.GetValueForKey("answer").GetUnsignedIntegerValue(), 42
+ )
+
+ self.expect(
+ "scripting extension list --instances ScriptedHook",
+ substrs=[
+ "instance_cmds.StopHook",
+ "Instances:",
+ "[0] ",
+ "[1] ",
+ "- answer: 42",
+ ],
+ )
+
+ def test_destroyed_instances_are_removed(self):
+ script_path = os.path.join(self.getSourceDir(), "instance_cmds.py")
+ self.runCmd("command script import " + script_path)
+ target = self.dbg.CreateTarget("")
+ self.assertTrue(target, VALID_TARGET)
+ self.runCmd("target stop-hook add -P instance_cmds.StopHook -k name -v first")
+ self.runCmd("target stop-hook add -P instance_cmds.StopHook -k name -v second")
+
+ def names():
+ group = self.find_group("instance_cmds.StopHook")
+ if not group:
+ return []
+ return [
+ i.GetValueForKey("args").GetValueForKey("name").GetStringValue(64)
+ for i in self.get_instances(group)
+ ]
+
+ self.assertEqual(names(), ["first", "second"])
+
+ self.runCmd("target stop-hook delete 1")
+ self.assertEqual(names(), ["second"])
+
+ self.assertTrue(self.dbg.DeleteTarget(target))
+ self.assertEqual(names(), [])
+
+ def test_path_without_command_script_import(self):
+ self.runCmd(
+ "script import sys; sys.path.insert(0, %r); import instance_cmds"
+ % self.getSourceDir()
+ )
+ self.runCmd("command script add -c instance_cmds.EchoCommand echo-cmd")
+ self.addTearDownHook(
+ lambda: self.runCmd("command script delete echo-cmd", check=False)
+ )
+
+ group = self.find_group("instance_cmds.EchoCommand")
+ self.assertIsNotNone(group, "scripted command instance is not listed")
+ self.assertEqual(
+ os.path.realpath(group.GetValueForKey("source_path").GetStringValue(4096)),
+ os.path.realpath(os.path.join(self.getSourceDir(), "instance_cmds.py")),
+ )
+
+ def test_invalid_extension_name(self):
+ self.expect(
+ "scripting extension list --instances NotAnExtension",
+ error=True,
+ substrs=["no scripted extension named 'NotAnExtension'"],
+ )
diff --git a/lldb/test/API/commands/scripting/extension/instance_cmds.py b/lldb/test/API/commands/scripting/extension/instance_cmds.py
new file mode 100644
index 00000000000000..0063a989dad98a
--- /dev/null
+++ b/lldb/test/API/commands/scripting/extension/instance_cmds.py
@@ -0,0 +1,17 @@
+import lldb
+
+
+class EchoCommand:
+ def __init__(self, debugger, internal_dict):
+ pass
+
+ def __call__(self, debugger, command, exe_ctx, result):
+ result.AppendMessage(command)
+
+
+class StopHook:
+ def __init__(self, target, extra_args, internal_dict):
+ pass
+
+ def handle_stop(self, exe_ctx, stream):
+ return True
diff --git a/lldb/unittests/Interpreter/TestScriptedInterface.cpp b/lldb/unittests/Interpreter/TestScriptedInterface.cpp
index 48fd98e601e0e7..cacfcfdccf76c3 100644
--- a/lldb/unittests/Interpreter/TestScriptedInterface.cpp
+++ b/lldb/unittests/Interpreter/TestScriptedInterface.cpp
@@ -8,6 +8,7 @@
#include "lldb/Interpreter/Interfaces/ScriptedCommandInterface.h"
#include "lldb/Interpreter/Interfaces/ScriptedInterface.h"
+#include "lldb/Interpreter/ScriptedInstanceRegistry.h"
#include "gtest/gtest.h"
using namespace lldb_private;
@@ -20,6 +21,8 @@ class DummyScriptedInterface : public ScriptedInterface {
GetAbstractMethodRequirements() const override {
return {};
}
+
+ llvm::StringRef GetPluginName() override { return "DummyPlugin"; }
};
class DummyScriptedCommandInterface : public ScriptedCommandInterface {
@@ -34,10 +37,56 @@ class DummyScriptedCommandInterface : public ScriptedCommandInterface {
GetAbstractMethodRequirements() const override {
return {};
}
+
+ llvm::StringRef GetPluginName() override { return "DummyPlugin"; }
+};
+
+class RegisteredScriptedInterface : public DummyScriptedInterface {
+public:
+ RegisteredScriptedInterface(
+ const lldb::ScriptedInstanceRegistrySP ®istry_sp,
+ llvm::StringRef class_name) {
+ RegisterInstance(registry_sp, class_name);
+ }
+
+ void Reregister(const lldb::ScriptedInstanceRegistrySP ®istry_sp) {
+ RegisterInstance(registry_sp, "module.Reregistered");
+ }
};
} // namespace
+TEST(ScriptedInterfaceTest, DestroyedInstanceIsUnregistered) {
+ auto registry_sp = std::make_shared<ScriptedInstanceRegistry>();
+ auto first = std::make_unique<RegisteredScriptedInterface>(registry_sp,
+ "module.First");
+ RegisteredScriptedInterface second(registry_sp, "module.Second");
+ ASSERT_EQ(registry_sp->GetInstances().size(), 2u);
+
+ first.reset();
+ std::vector<ScriptedInstanceInfo> instances = registry_sp->GetInstances();
+ ASSERT_EQ(instances.size(), 1u);
+ EXPECT_EQ(instances[0].class_name, "module.Second");
+}
+
+TEST(ScriptedInterfaceTest, ReregisteringReplacesEntry) {
+ auto registry_sp = std::make_shared<ScriptedInstanceRegistry>();
+ RegisteredScriptedInterface interface(registry_sp, "module.First");
+ interface.Reregister(registry_sp);
+
+ std::vector<ScriptedInstanceInfo> instances = registry_sp->GetInstances();
+ ASSERT_EQ(instances.size(), 1u);
+ EXPECT_EQ(instances[0].class_name, "module.Reregistered");
+}
+
+TEST(ScriptedInterfaceTest, InterfaceCanOutliveRegistry) {
+ auto registry_sp = std::make_shared<ScriptedInstanceRegistry>();
+ auto interface =
+ std::make_unique<RegisteredScriptedInterface>(registry_sp, "module.A");
+ registry_sp.reset();
+ interface.reset();
+}
+
TEST(ScriptedInterfaceTest, ExtensionsCannotBeRunDirectly) {
DummyScriptedInterface interface;
EXPECT_FALSE(interface.UserCanRunDirectly());
More information about the lldb-commits
mailing list