[Lldb-commits] [lldb] ee7d9f3 - [lldb] Add `scripting extension list --instances` (#226043)
via lldb-commits
lldb-commits at lists.llvm.org
Tue Oct 13 21:49:56 PDT 2026
Author: Med Ismail Bennani
Date: 2026-10-13T21:49:50-07:00
New Revision: ee7d9f3f4a13d4432223f05a2e10d6cf533e0484
URL: https://github.com/llvm/llvm-project/commit/ee7d9f3f4a13d4432223f05a2e10d6cf533e0484
DIFF: https://github.com/llvm/llvm-project/commit/ee7d9f3f4a13d4432223f05a2e10d6cf533e0484.diff
LOG: [lldb] Add `scripting extension list --instances` (#226043)
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 resolving, for each extension object, the
file of the module that defines its class, and attributing it to the
object's `ScriptedMetadata`. 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
```
This command is included to diagnostic report to help when investigating
scripting related bugs.
Assisted-by: Claude
Signed-off-by: Med Ismail Bennani <ismail at bennani.ma>
Added:
lldb/include/lldb/Interpreter/ScriptedInstanceRegistry.h
lldb/source/Interpreter/ScriptedInstanceRegistry.cpp
lldb/test/API/commands/scripting/extension/TestScriptingExtensionInstances.py
lldb/test/API/commands/scripting/extension/instance_cmds.py
lldb/test/API/commands/scripting/extension/instance_pkg/__init__.py
lldb/test/API/commands/scripting/extension/instance_pkg/cmds.py
lldb/test/Shell/Diagnostics/Inputs/scripted_command.py
lldb/test/Shell/Diagnostics/TestScriptedExtensionInstances.test
Modified:
lldb/include/lldb/Core/PluginManager.h
lldb/include/lldb/Interpreter/Interfaces/ScriptedInterface.h
lldb/include/lldb/Interpreter/ScriptInterpreter.h
lldb/include/lldb/Utility/ScriptedMetadata.h
lldb/include/lldb/lldb-forward.h
lldb/source/Commands/CommandObjectScripting.cpp
lldb/source/Commands/Options.td
lldb/source/Core/Diagnostics.cpp
lldb/source/Core/PluginManager.cpp
lldb/source/Interpreter/CMakeLists.txt
lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py
lldb/unittests/Interpreter/TestScriptedInterface.cpp
Removed:
################################################################################
diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h
index 94d90db2854ba..f38392ff7ac04 100644
--- a/lldb/include/lldb/Core/PluginManager.h
+++ b/lldb/include/lldb/Core/PluginManager.h
@@ -675,7 +675,7 @@ class PluginManager {
static bool UnregisterPlugin(ScriptedInterfaceCreateInstance create_callback);
- static uint32_t GetNumScriptedInterfaces();
+ static size_t GetNumScriptedInterfaces();
static llvm::StringRef GetScriptedInterfaceNameAtIndex(uint32_t idx);
diff --git a/lldb/include/lldb/Interpreter/Interfaces/ScriptedInterface.h b/lldb/include/lldb/Interpreter/Interfaces/ScriptedInterface.h
index 79c2f08a1820b..690d9cdc4954c 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"
@@ -20,6 +21,7 @@
#include "llvm/Support/Compiler.h"
+#include <cassert>
#include <optional>
#include <string>
@@ -27,7 +29,15 @@ namespace lldb_private {
class ScriptedInterface {
public:
ScriptedInterface() = default;
- virtual ~ScriptedInterface() = default;
+ virtual ~ScriptedInterface() { UnregisterInstance(); }
+
+ /// Delete the copy constructor. The copy would share the registry entry, and
+ /// the first one destroyed would unregister 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 +47,11 @@ class ScriptedInterface {
return m_scripted_metadata;
}
+ /// The name this interface is registered under with the PluginManager.
+ /// Every concrete scripted interface registers itself as a plugin, together
+ /// with the kind of extension it drives, so this name identifies that kind.
+ 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 +116,36 @@ class ScriptedInterface {
}
protected:
+ void RegisterInstance(const lldb::ScriptedInstanceRegistrySP ®istry_sp,
+ std::string class_name, FileSpec source_path,
+ StructuredData::DictionarySP args_sp) {
+ assert(registry_sp && "registering with an invalid registry");
+ assert(!m_registry_wp.lock() && "script object is already registered");
+ ScriptedInstanceInfo info;
+ info.plugin_name = GetPluginName();
+ info.class_name = std::move(class_name);
+ if (m_object_instance_sp)
+ info.object_address =
+ reinterpret_cast<uintptr_t>(m_object_instance_sp->GetValue());
+ info.source_path = std::move(source_path);
+ info.args_sp = std::move(args_sp);
+ 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 7ad530b3233f2..1b8cf605bee67 100644
--- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h
+++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h
@@ -22,11 +22,13 @@
#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 <memory>
#include <optional>
namespace lldb_private {
@@ -379,6 +381,10 @@ class ScriptInterpreter : public PluginInterface {
FileSpec extra_search_dir = {},
lldb::TargetSP loaded_into_target_sp = {});
+ 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 +528,10 @@ class ScriptInterpreter : public PluginInterface {
protected:
Debugger &m_debugger;
lldb::ScriptLanguage m_script_lang;
+
+private:
+ 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 0000000000000..331e716289acd
--- /dev/null
+++ b/lldb/include/lldb/Interpreter/ScriptedInstanceRegistry.h
@@ -0,0 +1,82 @@
+//===----------------------------------------------------------------------===//
+//
+// 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/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;
+ /// The interface's PluginManager name, from the string literal its
+ /// GetPluginNameStatic() returns, so this never dangles.
+ llvm::StringRef plugin_name;
+ std::string class_name;
+ FileSpec source_path;
+ StructuredData::DictionarySP args_sp;
+};
+
+/// The live instances of one extension class. Instances belong to the same
+/// group when they share a class name, source path and extension kind, so a
+/// class reused for several kinds of extension gets one group per kind.
+struct ScriptedInstanceGroup {
+ std::string class_name;
+ lldb::ScriptedExtension extension = lldb::eScriptedExtensionInvalid;
+ FileSpec source_path;
+ std::vector<ScriptedInstanceInfo> instances;
+
+ bool operator<(const ScriptedInstanceGroup &other) const;
+
+ 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);
+
+ void Remove(uint64_t id);
+
+ std::vector<ScriptedInstanceInfo> GetInstances() const;
+
+ std::vector<ScriptedInstanceGroup> GetInstanceGroups(
+ llvm::ArrayRef<lldb::ScriptedExtension> extensions = {}) const;
+
+private:
+ std::vector<ScriptedInstanceInfo> CopyInstances() const;
+
+ 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 fcbcc70bc36e3..38dad813070c5 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 f48f077e9b5ee..649f7b704d68e 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 7e6fdb7af2f40..d22bb2e23baef 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. ",
- "scripting extension list [--language <scripting-language> --] "
- "[--json --] [<extension-name> ...]") {
+ "List the available scripting extension templates, or with "
+ "--instances, the instantiated scripting extensions grouped by "
+ "class.",
+ "scripting extension list [--language <scripting-language>] "
+ "[--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 =
@@ -254,6 +266,53 @@ 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,
+ m_options.m_language))
+ 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 Debugger &debugger = GetDebugger();
+ const std::string separator = GetSeparator(debugger, 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, debugger, 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 66f54fa7e6493..8c1944716b391 100644
--- a/lldb/source/Commands/Options.td
+++ b/lldb/source/Commands/Options.td
@@ -1594,6 +1594,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/Core/Diagnostics.cpp b/lldb/source/Core/Diagnostics.cpp
index 08adcaebdf038..445ebc56d0f4e 100644
--- a/lldb/source/Core/Diagnostics.cpp
+++ b/lldb/source/Core/Diagnostics.cpp
@@ -237,6 +237,7 @@ static constexpr TriageCommand g_triage_commands[] = {
{"image lookup -va $pc", Requires::Frame},
{"register read", Requires::Frame},
{"frame variable", Requires::Frame},
+ {"scripting extension list --instances", Requires::Always},
};
static bool Available(Requires requirement, const ExecutionContext &exe_ctx) {
diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp
index 303f4701246c9..a6639f7213406 100644
--- a/lldb/source/Core/PluginManager.cpp
+++ b/lldb/source/Core/PluginManager.cpp
@@ -2095,7 +2095,7 @@ bool PluginManager::UnregisterPlugin(
return GetScriptedInterfaceInstances().UnregisterPlugin(create_callback);
}
-uint32_t PluginManager::GetNumScriptedInterfaces() {
+size_t PluginManager::GetNumScriptedInterfaces() {
return GetScriptedInterfaceInstances().GetSnapshot().size();
}
diff --git a/lldb/source/Interpreter/CMakeLists.txt b/lldb/source/Interpreter/CMakeLists.txt
index 8a9605b568865..17290df85e2ec 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/ScriptedInstanceRegistry.cpp b/lldb/source/Interpreter/ScriptedInstanceRegistry.cpp
new file mode 100644
index 0000000000000..1e6cdf57815a2
--- /dev/null
+++ b/lldb/source/Interpreter/ScriptedInstanceRegistry.cpp
@@ -0,0 +1,191 @@
+//===----------------------------------------------------------------------===//
+//
+// 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"
+
+using namespace lldb;
+using namespace lldb_private;
+
+static bool HasArgs(const ScriptedInstanceInfo &info) {
+ return info.args_sp && info.args_sp->GetSize();
+}
+
+static bool CreatedBefore(const ScriptedInstanceInfo &lhs,
+ const ScriptedInstanceInfo &rhs) {
+ return lhs.id < rhs.id;
+}
+
+uint64_t ScriptedInstanceRegistry::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 ScriptedInstanceRegistry::Remove(uint64_t id) {
+ std::lock_guard<std::mutex> guard(m_mutex);
+ m_instances.erase(id);
+}
+
+std::vector<ScriptedInstanceInfo>
+ScriptedInstanceRegistry::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;
+}
+
+std::vector<ScriptedInstanceInfo>
+ScriptedInstanceRegistry::GetInstances() const {
+ std::vector<ScriptedInstanceInfo> instances = CopyInstances();
+ llvm::sort(instances, CreatedBefore);
+ return instances;
+}
+
+bool ScriptedInstanceGroup::operator<(
+ const ScriptedInstanceGroup &other) const {
+ if (class_name != other.class_name)
+ return class_name < other.class_name;
+
+ const std::string path = source_path.GetPath();
+ const std::string other_path = other.source_path.GetPath();
+ if (path != other_path)
+ return path < other_path;
+
+ return extension < other.extension;
+}
+
+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 (size_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, CreatedBefore);
+ llvm::sort(groups);
+ 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 eb74e8caea40e..ccc340fe69e27 100644
--- a/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
+++ b/lldb/source/Plugins/ScriptInterpreter/Python/Interfaces/ScriptedPythonInterface.h
@@ -90,23 +90,32 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
"scripted Interface python object '__class__.__module__' attribute "
"is not a string");
- llvm::StringRef py_obj_module_str_ref = py_obj_module_str.GetString();
- PythonModule py_module = PythonModule::AddModule(py_obj_module_str_ref);
+ return GetModuleFile(py_obj_module_str.GetString());
+ }
+
+ /// The file the already imported \p module_name was loaded from, read from
+ /// its `__file__`. The caller must hold the GIL.
+ static llvm::Expected<FileSpec> GetModuleFile(llvm::StringRef module_name) {
+ using namespace python;
+
+ PythonModule py_module = PythonModule::AddModule(module_name);
if (!py_module.IsValid())
- return llvm::createStringError("failed to import '%s' module",
- py_obj_module_str_ref.data());
+ return llvm::createStringError(
+ llvm::formatv("failed to import '{0}' module", module_name).str());
PythonObject py_module_file = py_module.GetAttributeValue("__file__");
if (!py_module_file.IsValid())
return llvm::createStringError(
- "module '%s' is missing '__file__' attribute",
- py_obj_module_str_ref.data());
+ llvm::formatv("module '{0}' is missing '__file__' attribute",
+ module_name)
+ .str());
PythonString py_module_file_str = py_module_file.Str();
if (!py_module_file_str.IsValid())
return llvm::createStringError(
- "module '%s.__file__' attribute is not a string",
- py_obj_module_str_ref.data());
+ llvm::formatv("module '{0}.__file__' attribute is not a string",
+ module_name)
+ .str());
return FileSpec(py_module_file_str.GetString());
}
@@ -416,6 +425,37 @@ class ScriptedPythonInterface : virtual public ScriptedInterface {
m_object_instance_sp = StructuredData::GenericSP(
new StructuredPythonObject(std::move(result)));
+
+ // An object handed over by the script wasn't built from the metadata,
+ // which may even describe its creator, so name it after its own class and
+ // don't attribute the metadata's arguments to it.
+ std::string qualified_class_name = script_obj ? "" : class_name.str();
+ PythonString obj_module_name =
+ obj_class.GetAttributeValue("__module__").AsType<PythonString>();
+ if (qualified_class_name.empty() && obj_module_name.IsValid())
+ qualified_class_name =
+ llvm::formatv("{0}.{1}", obj_module_name.GetString(),
+ obj_class_name.GetString())
+ .str();
+
+ // Modules can be imported or reloaded without LLDB seeing it, so ask the
+ // class's module for its current file rather than remembering one.
+ FileSpec source_path;
+ if (obj_module_name.IsValid()) {
+ if (llvm::Expected<FileSpec> path_or_err =
+ GetModuleFile(obj_module_name.GetString()))
+ 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);
+ }
+ if (!script_obj)
+ m_scripted_metadata->SetSourcePath(source_path);
+ RegisterInstance(m_interpreter.GetScriptedInstanceRegistry(),
+ std::move(qualified_class_name), std::move(source_path),
+ script_obj ? nullptr : m_scripted_metadata->GetArgsSP());
+
return m_object_instance_sp;
}
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 0000000000000..bbf023d4c1d6d
--- /dev/null
+++ b/lldb/test/API/commands/scripting/extension/TestScriptingExtensionInstances.py
@@ -0,0 +1,187 @@
+"""
+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_path_of_class_in_submodule(self):
+ pkg_path = os.path.join(self.getSourceDir(), "instance_pkg")
+ self.runCmd("command script import " + pkg_path)
+ self.runCmd("command script add -c instance_pkg.cmds.EchoCommand echo-cmd")
+ self.addTearDownHook(
+ lambda: self.runCmd("command script delete echo-cmd", check=False)
+ )
+
+ # The module defining the class, not the package that was imported.
+ group = self.find_group("instance_pkg.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(pkg_path, "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 0000000000000..0063a989dad98
--- /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/test/API/commands/scripting/extension/instance_pkg/__init__.py b/lldb/test/API/commands/scripting/extension/instance_pkg/__init__.py
new file mode 100644
index 0000000000000..007f143bbaa18
--- /dev/null
+++ b/lldb/test/API/commands/scripting/extension/instance_pkg/__init__.py
@@ -0,0 +1 @@
+from . import cmds
diff --git a/lldb/test/API/commands/scripting/extension/instance_pkg/cmds.py b/lldb/test/API/commands/scripting/extension/instance_pkg/cmds.py
new file mode 100644
index 0000000000000..d792fb91f3dad
--- /dev/null
+++ b/lldb/test/API/commands/scripting/extension/instance_pkg/cmds.py
@@ -0,0 +1,9 @@
+import lldb
+
+
+class EchoCommand:
+ def __init__(self, debugger, internal_dict):
+ pass
+
+ def __call__(self, debugger, command, exe_ctx, result):
+ result.AppendMessage(command)
diff --git a/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py b/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py
index 8ca84a090a324..094286ae63da7 100644
--- a/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py
+++ b/lldb/test/API/functionalities/scripted_process/TestScriptedProcess.py
@@ -170,6 +170,78 @@ def cleanup():
self.assertIn("Failed to get scripted thread registers data.", log)
+ @skipUnlessDarwin
+ def test_scripted_extension_instances(self):
+ """Test that the threads and frames a scripted process creates in Python
+ are listed under their own classes, not under the process's."""
+ self.build()
+ target = self.dbg.CreateTarget(self.getBuildArtifact("a.out"))
+ self.assertTrue(target, VALID_TARGET)
+
+ os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"] = "1"
+
+ def cleanup():
+ del os.environ["SKIP_SCRIPTED_PROCESS_LAUNCH"]
+
+ self.addTearDownHook(cleanup)
+
+ script_path = os.path.join(self.getSourceDir(), "dummy_scripted_process.py")
+ self.runCmd("command script import " + script_path)
+
+ args = lldb.SBStructuredData()
+ self.assertSuccess(args.SetFromJSON('{"answer": 42}'))
+ launch_info = lldb.SBLaunchInfo(None)
+ launch_info.SetProcessPluginName("ScriptedProcess")
+ launch_info.SetScriptedProcessClassName(
+ "dummy_scripted_process.DummyScriptedProcess"
+ )
+ launch_info.SetScriptedProcessDictionary(args)
+
+ error = lldb.SBError()
+ process = target.Launch(launch_info, error)
+ self.assertTrue(process and process.IsValid(), PROCESS_IS_VALID)
+ # Fetching the frames is what wraps the Python frame objects.
+ self.assertGreater(process.GetThreadAtIndex(0).GetNumFrames(), 0)
+
+ result = lldb.SBCommandReturnObject()
+ self.dbg.GetCommandInterpreter().HandleCommand(
+ "scripting extension list --instances --json", result
+ )
+ self.assertTrue(result.Succeeded(), result.GetError())
+ data = lldb.SBStructuredData()
+ self.assertSuccess(data.SetFromJSON(result.GetOutput()))
+ groups = {}
+ for i in range(data.GetSize()):
+ group = data.GetItemAtIndex(i)
+ groups[group.GetValueForKey("class_name").GetStringValue(256)] = group
+
+ def args_of(class_name):
+ instance = groups[class_name].GetValueForKey("instances").GetItemAtIndex(0)
+ return instance.GetValueForKey("args")
+
+ process_class = "dummy_scripted_process.DummyScriptedProcess"
+ thread_class = "dummy_scripted_process.DummyScriptedThread"
+ frame_class = "dummy_scripted_process.DummyScriptedFrame"
+ self.assertIn(process_class, groups)
+ self.assertIn(thread_class, groups)
+ self.assertIn(frame_class, groups)
+ self.assertEqual(groups[process_class].GetValueForKey("instances").GetSize(), 1)
+ for class_name in (thread_class, frame_class):
+ self.assertEqual(
+ os.path.realpath(
+ groups[class_name]
+ .GetValueForKey("source_path")
+ .GetStringValue(4096)
+ ),
+ os.path.realpath(script_path),
+ )
+
+ # Only the process was created from the launch arguments.
+ self.assertEqual(
+ args_of(process_class).GetValueForKey("answer").GetIntegerValue(), 42
+ )
+ self.assertFalse(args_of(thread_class).IsValid())
+
@skipUnlessDarwin
def test_scripted_process_and_scripted_thread(self):
"""Test that we can launch an lldb scripted process using the SBAPI,
diff --git a/lldb/test/Shell/Diagnostics/Inputs/scripted_command.py b/lldb/test/Shell/Diagnostics/Inputs/scripted_command.py
new file mode 100644
index 0000000000000..11c758e12d377
--- /dev/null
+++ b/lldb/test/Shell/Diagnostics/Inputs/scripted_command.py
@@ -0,0 +1,15 @@
+import lldb
+
+
+class EchoCommand:
+ def __init__(self, debugger, internal_dict):
+ pass
+
+ def __call__(self, debugger, command, exe_ctx, result):
+ result.AppendMessage(command)
+
+
+def __lldb_init_module(debugger, internal_dict):
+ debugger.HandleCommand(
+ "command script add -c scripted_command.EchoCommand echo-cmd"
+ )
diff --git a/lldb/test/Shell/Diagnostics/TestScriptedExtensionInstances.test b/lldb/test/Shell/Diagnostics/TestScriptedExtensionInstances.test
new file mode 100644
index 0000000000000..8dac76d22e7d6
--- /dev/null
+++ b/lldb/test/Shell/Diagnostics/TestScriptedExtensionInstances.test
@@ -0,0 +1,13 @@
+# REQUIRES: python
+# Check that the diagnostics bundle lists the live scripted extensions, so a
+# triager can see which scripts were loaded and from where.
+
+# RUN: rm -rf %t
+# RUN: %lldb --no-lldbinit -b -o 'command script import %S/Inputs/scripted_command.py' \
+# RUN: -o 'diagnostics dump -d %t'
+# RUN: FileCheck %s --input-file=%t/commands.txt
+
+# CHECK: === scripting extension list --instances ===
+# CHECK: Class: scripted_command.EchoCommand
+# CHECK: Extension: ScriptedCommand
+# CHECK: Path: {{.*}}Inputs{{[/\\]}}scripted_command.py
diff --git a/lldb/unittests/Interpreter/TestScriptedInterface.cpp b/lldb/unittests/Interpreter/TestScriptedInterface.cpp
index 48fd98e601e0e..bc8ab3ad8888a 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,43 @@ 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.str(), /*source_path=*/{},
+ /*args_sp=*/nullptr);
+ }
};
} // 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, 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