diff --git a/lldb/include/lldb/Core/PluginManager.h b/lldb/include/lldb/Core/PluginManager.h index a6dab045adf27..b47a14bddcc89 100644 --- a/lldb/include/lldb/Core/PluginManager.h +++ b/lldb/include/lldb/Core/PluginManager.h @@ -18,10 +18,13 @@ #include "lldb/lldb-enumerations.h" #include "lldb/lldb-forward.h" #include "lldb/lldb-private-interfaces.h" +#include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/StringRef.h" +#include "llvm/Support/JSON.h" #include #include +#include #include #define LLDB_PLUGIN_DEFINE_ADV(ClassName, PluginName) \ @@ -54,12 +57,67 @@ struct RegisteredPluginInfo { bool enabled = false; }; +// Define some data structures to describe known plugin "namespaces". +// The PluginManager is organized into a series of static functions +// that operate on different types of plugins. For example SystemRuntime +// and ObjectFile plugins. +// +// The namespace name is used a prefix when matching plugin names. For example, +// if we have an "macosx" plugin in the "system-runtime" namespace then we will +// match a plugin name pattern against the "system-runtime.macosx" name. +// +// The plugin namespace here is used so we can operate on all the plugins +// of a given type so it is easy to enable or disable them as a group. +using GetPluginInfo = std::function()>; +using SetPluginEnabled = std::function; +struct PluginNamespace { + llvm::StringRef name; + GetPluginInfo get_info; + SetPluginEnabled set_enabled; +}; + class PluginManager { public: static void Initialize(); static void Terminate(); + // Support for enabling and disabling plugins. + + // Return the plugins that can be enabled or disabled by the user. + static llvm::ArrayRef GetPluginNamespaces(); + + // Generate a json object that describes the plugins that are available. + // This is a json representation of the plugin info returned by + // GetPluginNamespaces(). + // + // { + // : [ + // { + // "enabled": , + // "name": , + // }, + // ... + // ], + // ... + // } + // + // If pattern is given it will be used to filter the plugins that are + // are returned. The pattern filters the plugin names using the + // PluginManager::MatchPluginName() function. + static llvm::json::Object GetJSON(llvm::StringRef pattern = ""); + + // Return true if the pattern matches the plugin name. + // + // The pattern matches the name if it is exactly equal to the namespace name + // or if it is equal to the qualified name, which is the namespace name + // followed by a dot and the plugin name (e.g. "system-runtime.foo"). + // + // An empty pattern matches all plugins. + static bool MatchPluginName(llvm::StringRef pattern, + const PluginNamespace &plugin_ns, + const RegisteredPluginInfo &plugin); + // ABI static bool RegisterPlugin(llvm::StringRef name, llvm::StringRef description, ABICreateInstance create_callback); @@ -486,6 +544,12 @@ class PluginManager { static InstrumentationRuntimeCreateInstance GetInstrumentationRuntimeCreateCallbackAtIndex(uint32_t idx); + static std::vector + GetInstrumentationRuntimePluginInfo(); + + static bool SetInstrumentationRuntimePluginEnabled(llvm::StringRef name, + bool enabled); + // TypeSystem static bool RegisterPlugin(llvm::StringRef name, llvm::StringRef description, TypeSystemCreateInstance create_callback, diff --git a/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h b/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h index 1875ff6a048d4..8535dfcf46da5 100644 --- a/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h +++ b/lldb/include/lldb/Interpreter/CommandOptionArgumentTable.h @@ -314,6 +314,7 @@ static constexpr CommandObject::ArgumentTableEntry g_argument_table[] = { { lldb::eArgTypeModule, "module", lldb::CompletionType::eModuleCompletion, {}, { nullptr, false }, "The name of a module loaded into the current target." }, { lldb::eArgTypeCPUName, "cpu-name", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "The name of a CPU." }, { lldb::eArgTypeCPUFeatures, "cpu-features", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "The CPU feature string." }, + { lldb::eArgTypeManagedPlugin, "managed-plugin", lldb::CompletionType::eNoCompletion, {}, { nullptr, false }, "Plugins managed by the PluginManager" }, // clang-format on }; diff --git a/lldb/include/lldb/Target/Statistics.h b/lldb/include/lldb/Target/Statistics.h index ee365357fcf31..bffd361a8146b 100644 --- a/lldb/include/lldb/Target/Statistics.h +++ b/lldb/include/lldb/Target/Statistics.h @@ -174,12 +174,21 @@ struct StatisticsOptions { return !GetSummaryOnly(); } + void SetIncludePlugins(bool value) { m_include_plugins = value; } + bool GetIncludePlugins() const { + if (m_include_plugins.has_value()) + return m_include_plugins.value(); + // Default to true in both default mode and summary mode. + return true; + } + private: std::optional m_summary_only; std::optional m_load_all_debug_info; std::optional m_include_targets; std::optional m_include_modules; std::optional m_include_transcript; + std::optional m_include_plugins; }; /// A class that represents statistics about a TypeSummaryProviders invocations diff --git a/lldb/include/lldb/lldb-enumerations.h b/lldb/include/lldb/lldb-enumerations.h index 8e962428260f8..6bfd02dcd7e88 100644 --- a/lldb/include/lldb/lldb-enumerations.h +++ b/lldb/include/lldb/lldb-enumerations.h @@ -663,6 +663,7 @@ enum CommandArgumentType { eArgTypeModule, eArgTypeCPUName, eArgTypeCPUFeatures, + eArgTypeManagedPlugin, eArgTypeLastArg // Always keep this entry as the last entry in this // enumeration!! }; diff --git a/lldb/source/Commands/CommandObjectPlugin.cpp b/lldb/source/Commands/CommandObjectPlugin.cpp index f3108b8a768d2..cdc9006bf5261 100644 --- a/lldb/source/Commands/CommandObjectPlugin.cpp +++ b/lldb/source/Commands/CommandObjectPlugin.cpp @@ -7,6 +7,8 @@ //===----------------------------------------------------------------------===// #include "CommandObjectPlugin.h" +#include "lldb/Core/PluginManager.h" +#include "lldb/Host/OptionParser.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Interpreter/CommandReturnObject.h" @@ -46,12 +48,287 @@ class CommandObjectPluginLoad : public CommandObjectParsed { } }; +namespace { +// Helper function to perform an action on each matching plugin. +// The action callback is given the containing namespace along with plugin info +// for each matching plugin. +static int ActOnMatchingPlugins( + const llvm::StringRef pattern, + std::function &plugin_info)> + action) { + int num_matching = 0; + + for (const PluginNamespace &plugin_namespace : + PluginManager::GetPluginNamespaces()) { + + std::vector matching_plugins; + for (const RegisteredPluginInfo &plugin_info : + plugin_namespace.get_info()) { + if (PluginManager::MatchPluginName(pattern, plugin_namespace, + plugin_info)) + matching_plugins.push_back(plugin_info); + } + + if (!matching_plugins.empty()) { + num_matching += matching_plugins.size(); + action(plugin_namespace, matching_plugins); + } + } + + return num_matching; +} + +// Call the "SetEnable" function for each matching plugins. +// Used to share the majority of the code between the enable +// and disable commands. +int SetEnableOnMatchingPlugins(const llvm::StringRef &pattern, + CommandReturnObject &result, bool enabled) { + return ActOnMatchingPlugins( + pattern, [&](const PluginNamespace &plugin_namespace, + const std::vector &plugins) { + result.AppendMessage(plugin_namespace.name); + for (const auto &plugin : plugins) { + if (!plugin_namespace.set_enabled(plugin.name, enabled)) { + result.AppendErrorWithFormat("failed to enable plugin %s.%s", + plugin_namespace.name.data(), + plugin.name.data()); + continue; + } + + result.AppendMessageWithFormat( + " %s %-30s %s\n", enabled ? "[+]" : "[-]", plugin.name.data(), + plugin.description.data()); + } + }); +} + +static std::string ConvertJSONToPrettyString(const llvm::json::Value &json) { + std::string str; + llvm::raw_string_ostream os(str); + os << llvm::formatv("{0:2}", json).str(); + os.flush(); + return str; +} + +#define LLDB_OPTIONS_plugin_list +#include "CommandOptions.inc" + +// These option definitions are used by the plugin list command. +class PluginListCommandOptions : public Options { +public: + PluginListCommandOptions() = default; + + ~PluginListCommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + const int short_option = m_getopt_table[option_idx].val; + + switch (short_option) { + case 'j': + m_json_format = true; + break; + default: + llvm_unreachable("Unimplemented option"); + } + + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + m_json_format = false; + } + + llvm::ArrayRef GetDefinitions() override { + return llvm::ArrayRef(g_plugin_list_options); + } + + // Instance variables to hold the values for command options. + bool m_json_format = false; +}; +} // namespace + +class CommandObjectPluginList : public CommandObjectParsed { +public: + CommandObjectPluginList(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "plugin list", + "Report info about registered LLDB plugins.", + nullptr) { + AddSimpleArgumentList(eArgTypeManagedPlugin); + SetHelpLong(R"( +Display information about registered plugins. +The plugin information is formatted as shown below: + + + [+] Plugin #1 description + [-] Plugin #2 description + +An enabled plugin is marked with [+] and a disabled plugin is marked with [-]. + +Plugins can be listed by namespace and name with: + + plugin list [.] + +Plugins can be listed by namespace alone or with a fully qualified name. When listed +with just a namespace all plugins in that namespace are listed. When no arguments +are given all plugins are listed. + +Examples: +List all plugins + + (lldb) plugin list + +List all plugins in the system-runtime namespace + + (lldb) plugin list system-runtime + +List only the plugin 'foo' matching a fully qualified name exactly + + (lldb) plugin list system-runtime.foo +)"); + } + + ~CommandObjectPluginList() override = default; + + Options *GetOptions() override { return &m_options; } + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + size_t argc = command.GetArgumentCount(); + result.SetStatus(eReturnStatusSuccessFinishResult); + + // Create a temporary vector to hold the patterns to simplify the logic + // for the case when the user passes no patterns + std::vector patterns; + patterns.reserve(argc == 0 ? 1 : argc); + if (argc == 0) + patterns.push_back(""); + else + for (size_t i = 0; i < argc; ++i) + patterns.push_back(command[i].ref()); + + if (m_options.m_json_format) + OutputJsonFormat(patterns, result); + else + OutputTextFormat(patterns, result); + } + +private: + void OutputJsonFormat(const std::vector &patterns, + CommandReturnObject &result) { + llvm::json::Object obj; + bool found_empty = false; + for (const llvm::StringRef pattern : patterns) { + llvm::json::Object pat_obj = PluginManager::GetJSON(pattern); + if (pat_obj.empty()) { + found_empty = true; + result.AppendErrorWithFormat( + "Found no matching plugins for pattern '%s'", pattern.data()); + break; + } + for (auto &entry : pat_obj) { + obj[entry.first] = std::move(entry.second); + } + } + if (!found_empty) { + result.AppendMessage(ConvertJSONToPrettyString(std::move(obj))); + } + } + + void OutputTextFormat(const std::vector &patterns, + CommandReturnObject &result) { + for (const llvm::StringRef pattern : patterns) { + int num_matching = ActOnMatchingPlugins( + pattern, [&](const PluginNamespace &plugin_namespace, + const std::vector &plugins) { + result.AppendMessage(plugin_namespace.name); + for (auto &plugin : plugins) { + result.AppendMessageWithFormat( + " %s %-30s %s\n", plugin.enabled ? "[+]" : "[-]", + plugin.name.data(), plugin.description.data()); + } + }); + if (num_matching == 0) { + result.AppendErrorWithFormat( + "Found no matching plugins for pattern '%s'", pattern.data()); + break; + } + } + } + + PluginListCommandOptions m_options; +}; + +static void DoPluginEnableDisable(Args &command, CommandReturnObject &result, + bool enable) { + const char *name = enable ? "enable" : "disable"; + size_t argc = command.GetArgumentCount(); + if (argc == 0) { + result.AppendErrorWithFormat("'plugin %s' requires one or more arguments", + name); + return; + } + result.SetStatus(eReturnStatusSuccessFinishResult); + + for (size_t i = 0; i < argc; ++i) { + llvm::StringRef pattern = command[i].ref(); + int num_matching = SetEnableOnMatchingPlugins(pattern, result, enable); + + if (num_matching == 0) { + result.AppendErrorWithFormat( + "Found no matching plugins to %s for pattern '%s'", name, + pattern.data()); + break; + } + } +} + +class CommandObjectPluginEnable : public CommandObjectParsed { +public: + CommandObjectPluginEnable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "plugin enable", + "Enable registered LLDB plugins.", nullptr) { + AddSimpleArgumentList(eArgTypeManagedPlugin); + } + + ~CommandObjectPluginEnable() override = default; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + DoPluginEnableDisable(command, result, /*enable=*/true); + } +}; + +class CommandObjectPluginDisable : public CommandObjectParsed { +public: + CommandObjectPluginDisable(CommandInterpreter &interpreter) + : CommandObjectParsed(interpreter, "plugin disable", + "Disable registered LLDB plugins.", nullptr) { + AddSimpleArgumentList(eArgTypeManagedPlugin); + } + + ~CommandObjectPluginDisable() override = default; + +protected: + void DoExecute(Args &command, CommandReturnObject &result) override { + DoPluginEnableDisable(command, result, /*enable=*/false); + } +}; + CommandObjectPlugin::CommandObjectPlugin(CommandInterpreter &interpreter) : CommandObjectMultiword(interpreter, "plugin", "Commands for managing LLDB plugins.", "plugin []") { LoadSubCommand("load", CommandObjectSP(new CommandObjectPluginLoad(interpreter))); + LoadSubCommand("list", + CommandObjectSP(new CommandObjectPluginList(interpreter))); + LoadSubCommand("enable", + CommandObjectSP(new CommandObjectPluginEnable(interpreter))); + LoadSubCommand("disable", + CommandObjectSP(new CommandObjectPluginDisable(interpreter))); } CommandObjectPlugin::~CommandObjectPlugin() = default; diff --git a/lldb/source/Commands/CommandObjectStats.cpp b/lldb/source/Commands/CommandObjectStats.cpp index 7d333afc231ba..b77c44bdf5d09 100644 --- a/lldb/source/Commands/CommandObjectStats.cpp +++ b/lldb/source/Commands/CommandObjectStats.cpp @@ -103,6 +103,13 @@ class CommandObjectStatsDump : public CommandObjectParsed { else error = Status::FromError(bool_or_error.takeError()); break; + case 'p': + if (llvm::Expected bool_or_error = + OptionArgParser::ToBoolean("--plugins", option_arg)) + m_stats_options.SetIncludePlugins(*bool_or_error); + else + error = Status::FromError(bool_or_error.takeError()); + break; default: llvm_unreachable("Unimplemented option"); } diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td index cc579d767eb06..fba13b1c37a8d 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -683,6 +683,11 @@ let Command = "platform shell" in { Desc<"Shell interpreter path. This is the binary used to run the command.">; } +let Command = "plugin list" in { + def plugin_list_json : Option<"json", "j">, + Desc<"Output the plugin list in json format.">; +} + let Command = "process launch" in { def process_launch_stop_at_entry : Option<"stop-at-entry", "s">, Desc<"Stop at the entry point of the program when launching a process.">; @@ -1467,5 +1472,11 @@ let Command = "statistics dump" in { "scripts executed during a debug session. " "Defaults to true, unless the '--summary' mode is enabled, in which case " "this is turned off unless specified.">; - + def statistics_dump_plugins + : Option<"plugins", "p">, + Group<1>, + Arg<"Boolean">, + Desc<"Dump statistics for known plugins including name, order, and " + "enabled state. Defaults to true for both summary and default " + "mode.">; } diff --git a/lldb/source/Core/PluginManager.cpp b/lldb/source/Core/PluginManager.cpp index e6cb248ef31ce..62360a8f3be79 100644 --- a/lldb/source/Core/PluginManager.cpp +++ b/lldb/source/Core/PluginManager.cpp @@ -181,6 +181,56 @@ void PluginManager::Terminate() { plugin_map.clear(); } +llvm::ArrayRef PluginManager::GetPluginNamespaces() { + // Currently supported set of plugin namespaces. This will be expanded + // over time. + static PluginNamespace PluginNamespaces[] = { + {"system-runtime", PluginManager::GetSystemRuntimePluginInfo, + PluginManager::SetSystemRuntimePluginEnabled}, + {"instrumentation-runtime", + PluginManager::GetInstrumentationRuntimePluginInfo, + PluginManager::SetInstrumentationRuntimePluginEnabled}}; + + return PluginNamespaces; +} + +llvm::json::Object PluginManager::GetJSON(llvm::StringRef pattern) { + llvm::json::Object plugin_stats; + + for (const PluginNamespace &plugin_ns : GetPluginNamespaces()) { + llvm::json::Array namespace_stats; + + for (const RegisteredPluginInfo &plugin : plugin_ns.get_info()) { + if (MatchPluginName(pattern, plugin_ns, plugin)) { + llvm::json::Object plugin_json; + plugin_json.try_emplace("name", plugin.name); + plugin_json.try_emplace("enabled", plugin.enabled); + namespace_stats.emplace_back(std::move(plugin_json)); + } + } + if (!namespace_stats.empty()) + plugin_stats.try_emplace(plugin_ns.name, std::move(namespace_stats)); + } + + return plugin_stats; +} + +bool PluginManager::MatchPluginName(llvm::StringRef pattern, + const PluginNamespace &plugin_ns, + const RegisteredPluginInfo &plugin_info) { + // The empty pattern matches all plugins. + if (pattern.empty()) + return true; + + // Check if the pattern matches the namespace. + if (pattern == plugin_ns.name) + return true; + + // Check if the pattern matches the qualified name. + std::string qualified_name = (plugin_ns.name + "." + plugin_info.name).str(); + return pattern == qualified_name; +} + template struct PluginInstance { typedef Callback CallbackType; @@ -1499,6 +1549,16 @@ PluginManager::GetInstrumentationRuntimeCreateCallbackAtIndex(uint32_t idx) { return GetInstrumentationRuntimeInstances().GetCallbackAtIndex(idx); } +std::vector +PluginManager::GetInstrumentationRuntimePluginInfo() { + return GetInstrumentationRuntimeInstances().GetPluginInfoForAllInstances(); +} + +bool PluginManager::SetInstrumentationRuntimePluginEnabled(llvm::StringRef name, + bool enable) { + return GetInstrumentationRuntimeInstances().SetInstanceEnabled(name, enable); +} + #pragma mark TypeSystem struct TypeSystemInstance : public PluginInstance { diff --git a/lldb/source/Target/Statistics.cpp b/lldb/source/Target/Statistics.cpp index b5d2e7bda1edf..f38e627a03c4a 100644 --- a/lldb/source/Target/Statistics.cpp +++ b/lldb/source/Target/Statistics.cpp @@ -10,6 +10,7 @@ #include "lldb/Core/Debugger.h" #include "lldb/Core/Module.h" +#include "lldb/Core/PluginManager.h" #include "lldb/Interpreter/CommandInterpreter.h" #include "lldb/Symbol/SymbolFile.h" #include "lldb/Target/DynamicLoader.h" @@ -285,6 +286,7 @@ llvm::json::Value DebuggerStats::ReportStatistics( const bool include_targets = options.GetIncludeTargets(); const bool include_modules = options.GetIncludeModules(); const bool include_transcript = options.GetIncludeTranscript(); + const bool include_plugins = options.GetIncludePlugins(); json::Array json_targets; json::Array json_modules; @@ -464,6 +466,10 @@ llvm::json::Value DebuggerStats::ReportStatistics( } } + if (include_plugins) { + global_stats.try_emplace("plugins", PluginManager::GetJSON()); + } + return std::move(global_stats); } diff --git a/lldb/test/API/commands/statistics/basic/TestStats.py b/lldb/test/API/commands/statistics/basic/TestStats.py index 54881c13bcb68..4e635c34459e0 100644 --- a/lldb/test/API/commands/statistics/basic/TestStats.py +++ b/lldb/test/API/commands/statistics/basic/TestStats.py @@ -1022,3 +1022,89 @@ def test_multiple_targets(self): all_targets_stats = self.get_stats("--all-targets") self.assertIsNotNone(self.find_module_in_metrics(main_exe, all_targets_stats)) self.assertIsNotNone(self.find_module_in_metrics(second_exe, all_targets_stats)) + + # Return some level of the plugin stats hierarchy. + # Will return either the top-level node, the namespace node, or a specific + # plugin node based on requested values. + # + # If any of the requested keys are not found in the stats then return None. + # + # Plugin stats look like this: + # + # "plugins": { + # "system-runtime": [ + # { + # "enabled": true, + # "name": "systemruntime-macosx" + # } + # ] + # }, + def get_plugin_stats(self, debugger_stats, plugin_namespace=None, plugin_name=None): + # Get top level plugin stats. + if "plugins" not in debugger_stats: + return None + plugins = debugger_stats["plugins"] + if not plugin_namespace: + return plugins + + # Plugin namespace stats. + if plugin_namespace not in plugins: + return None + plugins_for_namespace = plugins[plugin_namespace] + if not plugin_name: + return plugins_for_namespace + + # Specific plugin stats. + for plugin in debugger_stats["plugins"][plugin_namespace]: + if plugin["name"] == plugin_name: + return plugin + return None + + def test_plugin_stats(self): + """ + Test "statistics dump" contains plugin info. + """ + self.build() + exe = self.getBuildArtifact("a.out") + target = self.createTestTarget(file_path=exe) + debugger_stats = self.get_stats() + + # Verify that the statistics dump contains the plugin information. + plugins = self.get_plugin_stats(debugger_stats) + self.assertIsNotNone(plugins) + + # Check for a known plugin namespace that should be in the stats. + system_runtime_plugins = self.get_plugin_stats(debugger_stats, "system-runtime") + self.assertIsNotNone(system_runtime_plugins) + + # Validate the keys exists for the bottom-level plugin stats. + plugin_keys_exist = [ + "name", + "enabled", + ] + for plugin in system_runtime_plugins: + self.verify_keys( + plugin, 'debugger_stats["plugins"]["system-runtime"]', plugin_keys_exist + ) + + # Check for a known plugin that is enabled by default. + system_runtime_macosx_plugin = self.get_plugin_stats( + debugger_stats, "system-runtime", "systemruntime-macosx" + ) + self.assertIsNotNone(system_runtime_macosx_plugin) + self.assertTrue(system_runtime_macosx_plugin["enabled"]) + + # Now disable the plugin and check the stats again. + # The stats should show the plugin is disabled. + self.runCmd("plugin disable system-runtime.systemruntime-macosx") + debugger_stats = self.get_stats() + system_runtime_macosx_plugin = self.get_plugin_stats( + debugger_stats, "system-runtime", "systemruntime-macosx" + ) + self.assertIsNotNone(system_runtime_macosx_plugin) + self.assertFalse(system_runtime_macosx_plugin["enabled"]) + + # Plugins should not show up in the stats when disabled with an option. + debugger_stats = self.get_stats("--plugins false") + plugins = self.get_plugin_stats(debugger_stats) + self.assertIsNone(plugins) diff --git a/lldb/test/Shell/Commands/command-plugin-enable+disable.test b/lldb/test/Shell/Commands/command-plugin-enable+disable.test new file mode 100644 index 0000000000000..af646e60abef0 --- /dev/null +++ b/lldb/test/Shell/Commands/command-plugin-enable+disable.test @@ -0,0 +1,87 @@ +# This test validates the plugin enable and disable commands. +# Currently it works only for system-runtime plugins and we only have one +# system runtime plugin so testing is a bit limited. +# +# Note that commands that return errors will stop running a script, so we +# have new RUN lines for any command that is expected to return an error. + +# RUN: %lldb -s %s -o exit 2>&1 | FileCheck %s + +# Test plugin list shows the default state which is expected to be enabled. +plugin list +# CHECK-LABEL: plugin list +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin disable disables a plugin. +plugin disable system-runtime.systemruntime-macosx +# CHECK-LABEL: plugin disable system-runtime.systemruntime-macosx +# CHECK: system-runtime +# CHECK: [-] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Make sure plugin list shows it disabled as well. +plugin list +# CHECK: system-runtime +# CHECK: [-] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin enable re-enables a plugin. +plugin enable system-runtime.systemruntime-macosx +# CHECK-LABEL: plugin enable system-runtime.systemruntime-macosx +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Make sure plugin list shows it enabled as well. +plugin list +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin disable with namespace works. +plugin disable system-runtime +# CHECK-LABEL: plugin disable system-runtime +# CHECK: system-runtime +# CHECK: [-] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin enable with namespace works. +plugin enable system-runtime +# CHECK-LABEL: plugin enable system-runtime +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin enable/disable for instrumentation plugin works. +plugin enable instrumentation-runtime +# CHECK-LABEL: plugin enable instrumentation-runtime +# CHECK: instrumentation-runtime +# CHECK: [+] AddressSanitizer +plugin disable instrumentation-runtime +# CHECK-LABEL: plugin disable instrumentation-runtime +# CHECK: instrumentation-runtime +# CHECK: [-] AddressSanitizer + +# Test plugin enable with multiple arguments. +plugin enable system-runtime instrumentation-runtime +# CHECK-LABEL: plugin enable system-runtime instrumentation-runtime +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries. +# CHECK: instrumentation-runtime +# CHECK: [+] AddressSanitizer AddressSanitizer instrumentation runtime plugin. + +# Test plugin disable with multiple arguments. +plugin disable system-runtime instrumentation-runtime +# CHECK-LABEL: plugin disable system-runtime instrumentation-runtime +# CHECK: system-runtime +# CHECK: [-] systemruntime-macosx System runtime plugin for Mac OS X native libraries. +# CHECK: instrumentation-runtime +# CHECK: [-] AddressSanitizer AddressSanitizer instrumentation runtime plugin. + +# Test plugin enable/disable for unknown plugin returns an error. +# RUN: %lldb -o "plugin enable some-plugin-that-does-not-exist" 2>&1 | FileCheck %s --check-prefix=ERROR_PLUGIN_NOT_FOUND +# RUN: %lldb -o "plugin disable some-plugin-that-does-not-exist" 2>&1 | FileCheck %s --check-prefix=ERROR_PLUGIN_NOT_FOUND +# RUN: %lldb -o "plugin enable system-runtime some-plugin-that-does-not-exist" 2>&1 | FileCheck %s --check-prefix=ERROR_PLUGIN_NOT_FOUND +# ERROR_PLUGIN_NOT_FOUND: error: Found no matching plugins + +# Test plugin enable/disable requires a plugin name. +# RUN: %lldb -o "plugin enable" 2>&1 | FileCheck %s --check-prefix=ERROR_ARGUMENTS_ENABLE +# ERROR_ARGUMENTS_ENABLE: error: 'plugin enable' requires one or more arguments + +# RUN: %lldb -o "plugin disable" 2>&1 | FileCheck %s --check-prefix=ERROR_ARGUMENTS_DISABLE +# ERROR_ARGUMENTS_DISABLE: error: 'plugin disable' requires one or more arguments diff --git a/lldb/test/Shell/Commands/command-plugin-list.test b/lldb/test/Shell/Commands/command-plugin-list.test new file mode 100644 index 0000000000000..9d3680d48cdd0 --- /dev/null +++ b/lldb/test/Shell/Commands/command-plugin-list.test @@ -0,0 +1,82 @@ +# This test validates the plugin list command. +# Currently it works only for system-runtime plugins and we only have one +# system runtime plugin so testing is a bit limited. +# +# Note that commands that return errors will stop running a script, so we +# have new RUN lines for any command that is expected to return an error. + +# RUN: %lldb -s %s -o exit 2>&1 | FileCheck %s + +# Test plugin list without an argument will list all plugins. +plugin list +# CHECK-LABEL: plugin list +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries +# CHECK: instrumentation-runtime +# CHECK: [+] AddressSanitizer AddressSanitizer instrumentation runtime plugin. + +# Test plugin list works with fully qualified name. +plugin list system-runtime.systemruntime-macosx +# CHECK-LABEL: plugin list system-runtime.systemruntime-macosx +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin list on plugin namespace works. +plugin list system-runtime +# CHECK-LABEL: plugin list system-runtime +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries + +# Test plugin list on multiple args works. +plugin list system-runtime instrumentation-runtime.AddressSanitizer +# CHECK-LABEL: plugin list system-runtime instrumentation-runtime.AddressSanitizer +# CHECK: system-runtime +# CHECK: [+] systemruntime-macosx System runtime plugin for Mac OS X native libraries +# CHECK: instrumentation-runtime +# CHECK: [+] AddressSanitizer AddressSanitizer instrumentation runtime plugin. + +# Test json output for plugin list. +plugin list --json +# CHECK-LABEL plugin list --json +# CHECK: { +# CHECK-DAG: "instrumentation-runtime": +# CHECK-DAG: "system-runtime": +# CHECK: } + +# Test json output for plugin list with a namespace +plugin list system-runtime --json +# CHECK-LABEL plugin list --json +# CHECK: { +# CHECK: "system-runtime": [ +# CHECK: { +# CHECK-DAG: "enabled": true +# CHECK-DAG: "name": "systemruntime-macosx" +# CHECK: } +# CHECK: ] +# CHECK: } + +# Test json output for listing multiple plugins +plugin list --json system-runtime instrumentation-runtime.AddressSanitizer +# CHECK-LABEL plugin list --json system-runtime instrumentation-runtime.AddressSanitizer +# CHECK: { +# CHECK-DAG: "instrumentation-runtime": +# CHECK-DAG: "name": "AddressSanitizer" +# CHECK-DAG: "system-runtime": +# CHECK: } + + +# Test plugin list does not match a plugin name by substring. +# RUN: %lldb -o "plugin list macosx" 2>&1 | FileCheck %s --check-prefix=ERROR_PLUGIN_NOT_FOUND + +# Test plugin list does not match a plugin namespace by substring. +# RUN: %lldb -o "plugin list system-runtime." 2>&1 | FileCheck %s --check-prefix=ERROR_PLUGIN_NOT_FOUND + +# Test plugin list returns an error for unknown second argument +# RUN: %lldb -o "plugin list system-runtime foo" 2>&1 | FileCheck %s --check-prefix=ERROR_PLUGIN_NOT_FOUND + +# Test plugin list returns an error for unknown second argument +# RUN: %lldb -o "plugin list --json system-runtime foo" 2>&1 | FileCheck %s --check-prefix=ERROR_PLUGIN_NOT_FOUND + +# Test plugin list for unknown plugin returns an error. +# RUN: %lldb -o "plugin list some-plugin-that-does-not-exist" 2>&1 | FileCheck %s --check-prefix=ERROR_PLUGIN_NOT_FOUND +# ERROR_PLUGIN_NOT_FOUND: error: Found no matching plugins diff --git a/lldb/unittests/Core/PluginManagerTest.cpp b/lldb/unittests/Core/PluginManagerTest.cpp index 9b0ce2286d273..c7a56b28a42fc 100644 --- a/lldb/unittests/Core/PluginManagerTest.cpp +++ b/lldb/unittests/Core/PluginManagerTest.cpp @@ -379,3 +379,93 @@ TEST_F(PluginManagerTest, UnRegisterSystemRuntimePluginChangesOrder) { ASSERT_EQ(PluginManager::GetSystemRuntimeCreateCallbackAtIndex(2), CreateSystemRuntimePluginB); } + +TEST_F(PluginManagerTest, MatchPluginName) { + PluginNamespace Foo{"foo", nullptr, nullptr}; + RegisteredPluginInfo Bar{"bar", "bar plugin ", true}; + RegisteredPluginInfo Baz{"baz", "baz plugin ", true}; + + // Empty pattern matches everything. + ASSERT_TRUE(PluginManager::MatchPluginName("", Foo, Bar)); + + // Plugin namespace matches all plugins in that namespace. + ASSERT_TRUE(PluginManager::MatchPluginName("foo", Foo, Bar)); + ASSERT_TRUE(PluginManager::MatchPluginName("foo", Foo, Baz)); + + // Fully qualified plugin name matches only that plugin. + ASSERT_TRUE(PluginManager::MatchPluginName("foo.bar", Foo, Bar)); + ASSERT_FALSE(PluginManager::MatchPluginName("foo.baz", Foo, Bar)); + + // Prefix match should not match. + ASSERT_FALSE(PluginManager::MatchPluginName("f", Foo, Bar)); + ASSERT_FALSE(PluginManager::MatchPluginName("foo.", Foo, Bar)); + ASSERT_FALSE(PluginManager::MatchPluginName("foo.ba", Foo, Bar)); +} + +TEST_F(PluginManagerTest, JsonFormat) { + RegisterMockSystemRuntimePlugins(); + + // We expect the following JSON output: + // { + // "system-runtime": [ + // { + // "enabled": true, + // "name": "a" + // }, + // { + // "enabled": true, + // "name": "b" + // }, + // { + // "enabled": true, + // "name": "c" + // } + // ] + // } + llvm::json::Object obj = PluginManager::GetJSON(); + + // We should have a "system-runtime" array in the top-level object. + llvm::json::Array *maybe_array = obj.getArray("system-runtime"); + ASSERT_TRUE(maybe_array != nullptr); + auto &array = *maybe_array; + ASSERT_EQ(array.size(), 3u); + + // Check plugin "a" info. + ASSERT_TRUE(array[0].getAsObject() != nullptr); + ASSERT_TRUE(array[0].getAsObject()->getString("name") == "a"); + ASSERT_TRUE(array[0].getAsObject()->getBoolean("enabled") == true); + + // Check plugin "b" info. + ASSERT_TRUE(array[1].getAsObject() != nullptr); + ASSERT_TRUE(array[1].getAsObject()->getString("name") == "b"); + ASSERT_TRUE(array[1].getAsObject()->getBoolean("enabled") == true); + + // Check plugin "c" info. + ASSERT_TRUE(array[2].getAsObject() != nullptr); + ASSERT_TRUE(array[2].getAsObject()->getString("name") == "c"); + ASSERT_TRUE(array[2].getAsObject()->getBoolean("enabled") == true); + + // Disabling a plugin should be reflected in the JSON output. + ASSERT_TRUE(PluginManager::SetSystemRuntimePluginEnabled("b", false)); + array = *PluginManager::GetJSON().getArray("system-runtime"); + ASSERT_TRUE(array[0].getAsObject()->getBoolean("enabled") == true); + ASSERT_TRUE(array[1].getAsObject()->getBoolean("enabled") == false); + ASSERT_TRUE(array[2].getAsObject()->getBoolean("enabled") == true); + + // Un-registering a plugin should be reflected in the JSON output. + ASSERT_TRUE(PluginManager::UnregisterPlugin(CreateSystemRuntimePluginB)); + array = *PluginManager::GetJSON().getArray("system-runtime"); + ASSERT_EQ(array.size(), 2u); + ASSERT_TRUE(array[0].getAsObject()->getString("name") == "a"); + ASSERT_TRUE(array[1].getAsObject()->getString("name") == "c"); + + // Filtering the JSON output should only include the matching plugins. + array = + *PluginManager::GetJSON("system-runtime.c").getArray("system-runtime"); + ASSERT_EQ(array.size(), 1u); + ASSERT_TRUE(array[0].getAsObject()->getString("name") == "c"); + + // Empty JSON output is allowed if there are no matching plugins. + obj = PluginManager::GetJSON("non-existent-plugin"); + ASSERT_TRUE(obj.empty()); +}