diff options
16 files changed, 1831 insertions, 106 deletions
diff --git a/lldb/bindings/python/CMakeLists.txt b/lldb/bindings/python/CMakeLists.txt index c941f76..73b1239 100644 --- a/lldb/bindings/python/CMakeLists.txt +++ b/lldb/bindings/python/CMakeLists.txt @@ -96,13 +96,15 @@ function(finish_swig_python swig_target lldb_python_bindings_dir lldb_python_tar ${lldb_python_target_dir} "utils" FILES "${LLDB_SOURCE_DIR}/examples/python/in_call_stack.py" - "${LLDB_SOURCE_DIR}/examples/python/symbolication.py") + "${LLDB_SOURCE_DIR}/examples/python/symbolication.py" + ) create_python_package( ${swig_target} ${lldb_python_target_dir} "plugins" FILES + "${LLDB_SOURCE_DIR}/examples/python/templates/parsed_cmd.py" "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_process.py" "${LLDB_SOURCE_DIR}/examples/python/templates/scripted_platform.py" "${LLDB_SOURCE_DIR}/examples/python/templates/operating_system.py") diff --git a/lldb/bindings/python/python-wrapper.swig b/lldb/bindings/python/python-wrapper.swig index 17bc7b1f..1370afc 100644 --- a/lldb/bindings/python/python-wrapper.swig +++ b/lldb/bindings/python/python-wrapper.swig @@ -287,12 +287,12 @@ PythonObject lldb_private::python::SWIGBridge::LLDBSwigPythonCreateScriptedThrea } bool lldb_private::python::SWIGBridge::LLDBSWIGPythonCallThreadPlan( - void *implementor, const char *method_name, lldb_private::Event *event, + void *implementer, const char *method_name, lldb_private::Event *event, bool &got_error) { got_error = false; PyErr_Cleaner py_err_cleaner(false); - PythonObject self(PyRefType::Borrowed, static_cast<PyObject *>(implementor)); + PythonObject self(PyRefType::Borrowed, static_cast<PyObject *>(implementer)); auto pfunc = self.ResolveName<PythonCallable>(method_name); if (!pfunc.IsAllocated()) @@ -325,12 +325,12 @@ bool lldb_private::python::SWIGBridge::LLDBSWIGPythonCallThreadPlan( } bool lldb_private::python::SWIGBridge::LLDBSWIGPythonCallThreadPlan( - void *implementor, const char *method_name, lldb_private::Stream *stream, + void *implementer, const char *method_name, lldb_private::Stream *stream, bool &got_error) { got_error = false; PyErr_Cleaner py_err_cleaner(false); - PythonObject self(PyRefType::Borrowed, static_cast<PyObject *>(implementor)); + PythonObject self(PyRefType::Borrowed, static_cast<PyObject *>(implementer)); auto pfunc = self.ResolveName<PythonCallable>(method_name); if (!pfunc.IsAllocated()) @@ -831,6 +831,29 @@ bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallCommandObject( return true; } +#include "lldb/Interpreter/CommandReturnObject.h" + +bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallParsedCommandObject( + PyObject *implementor, lldb::DebuggerSP debugger, lldb_private::StructuredDataImpl &args_impl, + lldb_private::CommandReturnObject &cmd_retobj, + lldb::ExecutionContextRefSP exe_ctx_ref_sp) { + + PyErr_Cleaner py_err_cleaner(true); + + PythonObject self(PyRefType::Borrowed, implementor); + auto pfunc = self.ResolveName<PythonCallable>("__call__"); + + if (!pfunc.IsAllocated()) { + cmd_retobj.AppendError("Could not find '__call__' method in implementation class"); + return false; + } + + pfunc(SWIGBridge::ToSWIGWrapper(std::move(debugger)), SWIGBridge::ToSWIGWrapper(args_impl), + SWIGBridge::ToSWIGWrapper(exe_ctx_ref_sp), SWIGBridge::ToSWIGWrapper(cmd_retobj).obj()); + + return true; +} + PythonObject lldb_private::python::SWIGBridge::LLDBSWIGPythonCreateOSPlugin( const char *python_class_name, const char *session_dictionary_name, const lldb::ProcessSP &process_sp) { diff --git a/lldb/examples/python/cmdtemplate.py b/lldb/examples/python/cmdtemplate.py index a3c30f3..49a0836 100644 --- a/lldb/examples/python/cmdtemplate.py +++ b/lldb/examples/python/cmdtemplate.py @@ -11,115 +11,84 @@ import inspect import lldb -import optparse -import shlex import sys +from lldb.plugins.parsed_cmd import ParsedCommand - -class FrameStatCommand: +class FrameStatCommand(ParsedCommand): program = "framestats" @classmethod def register_lldb_command(cls, debugger, module_name): - parser = cls.create_options() - cls.__doc__ = parser.format_help() - # Add any commands contained in this module to LLDB - command = "command script add -o -c %s.%s %s" % ( - module_name, - cls.__name__, - cls.program, - ) - debugger.HandleCommand(command) + ParsedCommandBase.do_register_cmd(cls, debugger, module_name) print( 'The "{0}" command has been installed, type "help {0}" or "{0} ' '--help" for detailed help.'.format(cls.program) ) - @classmethod - def create_options(cls): - usage = "usage: %prog [options]" - description = ( - "This command is meant to be an example of how to make " - "an LLDB command that does something useful, follows " - "best practices, and exploits the SB API. " - "Specifically, this command computes the aggregate " - "and average size of the variables in the current " - "frame and allows you to tweak exactly which variables " - "are to be accounted in the computation." - ) + def setup_command_definition(self): - # Pass add_help_option = False, since this keeps the command in line - # with lldb commands, and we wire up "help command" to work by - # providing the long & short help methods below. - parser = optparse.OptionParser( - description=description, - prog=cls.program, - usage=usage, - add_help_option=False, + self.ov_parser.add_option( + "i", + "in-scope", + help = "in_scope_only = True", + value_type = lldb.eArgTypeBoolean, + dest = "bool_arg", + default = True, ) - parser.add_option( - "-i", - "--in-scope", - action="store_true", - dest="inscope", - help="in_scope_only = True", + self.ov_parser.add_option( + "i", + "in-scope", + help = "in_scope_only = True", + value_type = lldb.eArgTypeBoolean, + dest = "inscope", default=True, ) - - parser.add_option( - "-a", - "--arguments", - action="store_true", - dest="arguments", - help="arguments = True", - default=True, + + self.ov_parser.add_option( + "a", + "arguments", + help = "arguments = True", + value_type = lldb.eArgTypeBoolean, + dest = "arguments", + default = True, ) - parser.add_option( - "-l", - "--locals", - action="store_true", - dest="locals", - help="locals = True", - default=True, + self.ov_parser.add_option( + "l", + "locals", + help = "locals = True", + value_type = lldb.eArgTypeBoolean, + dest = "locals", + default = True, ) - parser.add_option( - "-s", - "--statics", - action="store_true", - dest="statics", - help="statics = True", - default=True, + self.ov_parser.add_option( + "s", + "statics", + help = "statics = True", + value_type = lldb.eArgTypeBoolean, + dest = "statics", + default = True, ) - return parser - def get_short_help(self): return "Example command for use in debugging" def get_long_help(self): - return self.help_string + return ("This command is meant to be an example of how to make " + "an LLDB command that does something useful, follows " + "best practices, and exploits the SB API. " + "Specifically, this command computes the aggregate " + "and average size of the variables in the current " + "frame and allows you to tweak exactly which variables " + "are to be accounted in the computation.") + def __init__(self, debugger, unused): - self.parser = self.create_options() - self.help_string = self.parser.format_help() + super().__init__(debugger, unused) def __call__(self, debugger, command, exe_ctx, result): - # Use the Shell Lexer to properly parse up command options just like a - # shell would - command_args = shlex.split(command) - - try: - (options, args) = self.parser.parse_args(command_args) - except: - # if you don't handle exceptions, passing an incorrect argument to - # the OptionParser will cause LLDB to exit (courtesy of OptParse - # dealing with argument errors by throwing SystemExit) - result.SetError("option parsing failed") - return - # Always get program state from the lldb.SBExecutionContext passed # in as exe_ctx frame = exe_ctx.GetFrame() @@ -128,7 +97,7 @@ class FrameStatCommand: return variables_list = frame.GetVariables( - options.arguments, options.locals, options.statics, options.inscope + self.ov_parser.arguments, self.ov_parser.locals, self.ov_parser.statics, self.ov_parser.inscope ) variables_count = variables_list.GetSize() if variables_count == 0: diff --git a/lldb/examples/python/templates/parsed_cmd.py b/lldb/examples/python/templates/parsed_cmd.py new file mode 100644 index 0000000..61ea57c --- /dev/null +++ b/lldb/examples/python/templates/parsed_cmd.py @@ -0,0 +1,360 @@ +""" +This module implements a couple of utility classes to make writing +lldb parsed commands more Pythonic. +The way to use it is to make a class for your command that inherits from ParsedCommandBase. +That will make an LLDBOptionValueParser which you will use for your +option definition, and to fetch option values for the current invocation +of your command. Access to the OV parser is through: + +ParsedCommandBase.get_parser() + +Next, implement setup_command_definition() in your new command class, and call: + + self.get_parser().add_option() + +to add all your options. The order doesn't matter for options, lldb will sort them +alphabetically for you when it prints help. + +Similarly you can define the arguments with: + + self.get_parser().add_argument() + +At present, lldb doesn't do as much work as it should verifying arguments, it +only checks that commands that take no arguments don't get passed arguments. + +Then implement the execute function for your command as: + + def __call__(self, debugger, args_list, exe_ctx, result): + +The arguments will be a list of strings. + +You can access the option values using the 'dest' string you passed in when defining the option. +And if you need to know whether a given option was set by the user or not, you can +use the was_set API. + +So for instance, if you have an option whose "dest" is "my_option", then: + + self.get_parser().my_option + +will fetch the value, and: + + self.get_parser().was_set("my_option") + +will return True if the user set this option, and False if it was left at its default +value. + +There are example commands in the lldb testsuite at: + +llvm-project/lldb/test/API/commands/command/script/add/test_commands.py +""" +import inspect +import lldb +import sys +from abc import abstractmethod + +class LLDBOptionValueParser: + """ + This class holds the option definitions for the command, and when + the command is run, you can ask the parser for the current values. """ + + def __init__(self): + # This is a dictionary of dictionaries. The key is the long option + # name, and the value is the rest of the definition. + self.options_dict = {} + self.args_array = [] + + # Some methods to translate common value types. Should return a + # tuple of the value and an error value (True => error) if the + # type can't be converted. These are called internally when the + # command line is parsed into the 'dest' properties, you should + # not need to call them directly. + # FIXME: Need a way to push the conversion error string back to lldb. + @staticmethod + def to_bool(in_value): + error = True + value = False + if type(in_value) != str or len(in_value) == 0: + return (value, error) + + low_in = in_value.lower() + if low_in in ["y", "yes", "t", "true", "1"]: + value = True + error = False + + if not value and low_in in ["n", "no", "f", "false", "0"]: + value = False + error = False + + return (value, error) + + @staticmethod + def to_int(in_value): + #FIXME: Not doing errors yet... + return (int(in_value), False) + + @staticmethod + def to_unsigned(in_value): + # FIXME: find an unsigned converter... + # And handle errors. + return (int(in_value), False) + + translators = { + lldb.eArgTypeBoolean : to_bool, + lldb.eArgTypeBreakpointID : to_unsigned, + lldb.eArgTypeByteSize : to_unsigned, + lldb.eArgTypeCount : to_unsigned, + lldb.eArgTypeFrameIndex : to_unsigned, + lldb.eArgTypeIndex : to_unsigned, + lldb.eArgTypeLineNum : to_unsigned, + lldb.eArgTypeNumLines : to_unsigned, + lldb.eArgTypeNumberPerLine : to_unsigned, + lldb.eArgTypeOffset : to_int, + lldb.eArgTypeThreadIndex : to_unsigned, + lldb.eArgTypeUnsignedInteger : to_unsigned, + lldb.eArgTypeWatchpointID : to_unsigned, + lldb.eArgTypeColumnNum : to_unsigned, + lldb.eArgTypeRecognizerID : to_unsigned, + lldb.eArgTypeTargetID : to_unsigned, + lldb.eArgTypeStopHookID : to_unsigned + } + + @classmethod + def translate_value(cls, value_type, value): + try: + return cls.translators[value_type](value) + except KeyError: + # If we don't have a translator, return the string value. + return (value, False) + + # FIXME: would this be better done on the C++ side? + # The common completers are missing some useful ones. + # For instance there really should be a common Type completer + # And an "lldb command name" completer. + completion_table = { + lldb.eArgTypeAddressOrExpression : lldb.eVariablePathCompletion, + lldb.eArgTypeArchitecture : lldb.eArchitectureCompletion, + lldb.eArgTypeBreakpointID : lldb.eBreakpointCompletion, + lldb.eArgTypeBreakpointIDRange : lldb.eBreakpointCompletion, + lldb.eArgTypeBreakpointName : lldb.eBreakpointNameCompletion, + lldb.eArgTypeClassName : lldb.eSymbolCompletion, + lldb.eArgTypeDirectoryName : lldb.eDiskDirectoryCompletion, + lldb.eArgTypeExpression : lldb.eVariablePathCompletion, + lldb.eArgTypeExpressionPath : lldb.eVariablePathCompletion, + lldb.eArgTypeFilename : lldb.eDiskFileCompletion, + lldb.eArgTypeFrameIndex : lldb.eFrameIndexCompletion, + lldb.eArgTypeFunctionName : lldb.eSymbolCompletion, + lldb.eArgTypeFunctionOrSymbol : lldb.eSymbolCompletion, + lldb.eArgTypeLanguage : lldb.eTypeLanguageCompletion, + lldb.eArgTypePath : lldb.eDiskFileCompletion, + lldb.eArgTypePid : lldb.eProcessIDCompletion, + lldb.eArgTypeProcessName : lldb.eProcessNameCompletion, + lldb.eArgTypeRegisterName : lldb.eRegisterCompletion, + lldb.eArgTypeRunArgs : lldb.eDiskFileCompletion, + lldb.eArgTypeShlibName : lldb.eModuleCompletion, + lldb.eArgTypeSourceFile : lldb.eSourceFileCompletion, + lldb.eArgTypeSymbol : lldb.eSymbolCompletion, + lldb.eArgTypeThreadIndex : lldb.eThreadIndexCompletion, + lldb.eArgTypeVarName : lldb.eVariablePathCompletion, + lldb.eArgTypePlatform : lldb.ePlatformPluginCompletion, + lldb.eArgTypeWatchpointID : lldb.eWatchpointIDCompletion, + lldb.eArgTypeWatchpointIDRange : lldb.eWatchpointIDCompletion, + lldb.eArgTypeModuleUUID : lldb.eModuleUUIDCompletion, + lldb.eArgTypeStopHookID : lldb.eStopHookIDCompletion + } + + @classmethod + def determine_completion(cls, arg_type): + return cls.completion_table.get(arg_type, lldb.eNoCompletion) + + def add_argument_set(self, arguments): + self.args_array.append(arguments) + + def get_option_element(self, long_name): + return self.options_dict.get(long_name, None) + + def is_enum_opt(self, opt_name): + elem = self.get_option_element(opt_name) + if not elem: + return False + return "enum_values" in elem + + def option_parsing_started(self): + """ This makes the ivars for all the "dest" values in the array and gives them + their default values. You should not have to call this by hand, though if + you have some option that needs to do some work when a new command invocation + starts, you can override this to handle your special option. """ + for key, elem in self.options_dict.items(): + elem['_value_set'] = False + try: + object.__setattr__(self, elem["dest"], elem["default"]) + except AttributeError: + # It isn't an error not to have a "dest" variable name, you'll + # just have to manage this option's value on your own. + continue + + def set_enum_value(self, enum_values, input): + """ This sets the value for an enum option, you should not have to call this + by hand. """ + candidates = [] + for candidate in enum_values: + # The enum_values are a two element list of value & help string. + value = candidate[0] + if value.startswith(input): + candidates.append(value) + + if len(candidates) == 1: + return (candidates[0], False) + else: + return (input, True) + + def set_option_value(self, exe_ctx, opt_name, opt_value): + """ This sets a single option value. This will handle most option + value types, but if you have an option that has some complex behavior, + you can override this to implement that behavior, and then pass the + rest of the options to the base class implementation. """ + elem = self.get_option_element(opt_name) + if not elem: + return False + + if "enum_values" in elem: + (value, error) = self.set_enum_value(elem["enum_values"], opt_value) + else: + (value, error) = __class__.translate_value(elem["value_type"], opt_value) + + if error: + return False + + object.__setattr__(self, elem["dest"], value) + elem["_value_set"] = True + return True + + def was_set(self, opt_name): + """ Call this in the __call__ method of your command to determine + whether this option was set on the command line. It is sometimes + useful to know whether an option has the default value because the + user set it explicitly (was_set -> True) or not. """ + + elem = self.get_option_element(opt_name) + if not elem: + return False + try: + return elem["_value_set"] + except AttributeError: + return False + + def add_option(self, short_option, long_option, help, default, + dest = None, required=False, groups = None, + value_type=lldb.eArgTypeNone, completion_type=None, + enum_values=None): + """ + short_option: one character, must be unique, not required + long_option: no spaces, must be unique, required + help: a usage string for this option, will print in the command help + default: the initial value for this option (if it has a value) + dest: the name of the property that gives you access to the value for + this value. Defaults to the long option if not provided. + required: if true, this option must be provided or the command will error out + groups: Which "option groups" does this option belong to + value_type: one of the lldb.eArgType enum values. Some of the common arg + types also have default completers, which will be applied automatically. + completion_type: currently these are values form the lldb.CompletionType enum, I + haven't done custom completions yet. + enum_values: An array of duples: ["element_name", "element_help"]. If provided, + only one of the enum elements is allowed. The value will be the + element_name for the chosen enum element as a string. + """ + if not dest: + dest = long_option + + if not completion_type: + completion_type = self.determine_completion(value_type) + + dict = {"short_option" : short_option, + "required" : required, + "help" : help, + "value_type" : value_type, + "completion_type" : completion_type, + "dest" : dest, + "default" : default} + + if enum_values: + dict["enum_values"] = enum_values + if groups: + dict["groups"] = groups + + self.options_dict[long_option] = dict + + def make_argument_element(self, arg_type, repeat = "optional", groups = None): + element = {"arg_type" : arg_type, "repeat" : repeat} + if groups: + element["groups"] = groups + return element + +class ParsedCommand: + def __init__(self, debugger, unused): + self.debugger = debugger + self.ov_parser = LLDBOptionValueParser() + self.setup_command_definition() + + def get_options_definition(self): + return self.get_parser().options_dict + + def get_flags(self): + return 0 + + def get_args_definition(self): + return self.get_parser().args_array + + # The base class will handle calling these methods + # when appropriate. + + def option_parsing_started(self): + self.get_parser().option_parsing_started() + + def set_option_value(self, exe_ctx, opt_name, opt_value): + return self.get_parser().set_option_value(exe_ctx, opt_name, opt_value) + + def get_parser(self): + """Returns the option value parser for this command. + When defining the command, use the parser to add + argument and option definitions to the command. + When you are in the command callback, the parser + gives you access to the options passes to this + invocation""" + + return self.ov_parser + + # These are the two "pure virtual" methods: + @abstractmethod + def __call__(self, debugger, args_array, exe_ctx, result): + """This is the command callback. The option values are + provided by the 'dest' properties on the parser. + + args_array: This is the list of arguments provided. + exe_ctx: Gives the SBExecutionContext on which the + command should operate. + result: Any results of the command should be + written into this SBCommandReturnObject. + """ + raise NotImplementedError() + + @abstractmethod + def setup_command_definition(self): + """This will be called when your command is added to + the command interpreter. Here is where you add your + options and argument definitions for the command.""" + raise NotImplementedError() + + @staticmethod + def do_register_cmd(cls, debugger, module_name): + """ Add any commands contained in this module to LLDB """ + command = "command script add -o -p -c %s.%s %s" % ( + module_name, + cls.__name__, + cls.program, + ) + debugger.HandleCommand(command) + print( + 'The "{0}" command has been installed, type "help {0}"' + 'for detailed help.'.format(cls.program) + ) diff --git a/lldb/include/lldb/Interpreter/CommandObject.h b/lldb/include/lldb/Interpreter/CommandObject.h index 7b427de..b99de56 100644 --- a/lldb/include/lldb/Interpreter/CommandObject.h +++ b/lldb/include/lldb/Interpreter/CommandObject.h @@ -224,7 +224,10 @@ public: void GetFormattedCommandArguments(Stream &str, uint32_t opt_set_mask = LLDB_OPT_SET_ALL); - bool IsPairType(ArgumentRepetitionType arg_repeat_type); + static bool IsPairType(ArgumentRepetitionType arg_repeat_type); + + static std::optional<ArgumentRepetitionType> + ArgRepetitionFromString(llvm::StringRef string); bool ParseOptions(Args &args, CommandReturnObject &result); diff --git a/lldb/include/lldb/Interpreter/ScriptInterpreter.h b/lldb/include/lldb/Interpreter/ScriptInterpreter.h index b941f60..932eaa8 100644 --- a/lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ b/lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -473,6 +473,14 @@ public: return false; } + virtual bool RunScriptBasedParsedCommand( + StructuredData::GenericSP impl_obj_sp, Args& args, + ScriptedCommandSynchronicity synchronicity, + lldb_private::CommandReturnObject &cmd_retobj, Status &error, + const lldb_private::ExecutionContext &exe_ctx) { + return false; + } + virtual bool RunScriptFormatKeyword(const char *impl_function, Process *process, std::string &output, Status &error) { @@ -517,6 +525,27 @@ public: dest.clear(); return false; } + + virtual StructuredData::ObjectSP + GetOptionsForCommandObject(StructuredData::GenericSP cmd_obj_sp) { + return {}; + } + + virtual StructuredData::ObjectSP + GetArgumentsForCommandObject(StructuredData::GenericSP cmd_obj_sp) { + return {}; + } + + virtual bool SetOptionValueForCommandObject( + StructuredData::GenericSP cmd_obj_sp, ExecutionContext *exe_ctx, + llvm::StringRef long_option, llvm::StringRef value) { + return false; + } + + virtual void OptionParsingStartedForCommandObject( + StructuredData::GenericSP cmd_obj_sp) { + return; + } virtual uint32_t GetFlagsForCommandObject(StructuredData::GenericSP cmd_obj_sp) { diff --git a/lldb/source/Commands/CommandObjectCommands.cpp b/lldb/source/Commands/CommandObjectCommands.cpp index a51e5ab..3dfd452 100644 --- a/lldb/source/Commands/CommandObjectCommands.cpp +++ b/lldb/source/Commands/CommandObjectCommands.cpp @@ -1151,13 +1151,16 @@ private: CompletionType m_completion_type = eNoCompletion; }; -class CommandObjectScriptingObject : public CommandObjectRaw { +/// This class implements a "raw" scripted command. lldb does no parsing of the +/// command line, instead passing the line unaltered (except for backtick +/// substitution). +class CommandObjectScriptingObjectRaw : public CommandObjectRaw { public: - CommandObjectScriptingObject(CommandInterpreter &interpreter, - std::string name, - StructuredData::GenericSP cmd_obj_sp, - ScriptedCommandSynchronicity synch, - CompletionType completion_type) + CommandObjectScriptingObjectRaw(CommandInterpreter &interpreter, + std::string name, + StructuredData::GenericSP cmd_obj_sp, + ScriptedCommandSynchronicity synch, + CompletionType completion_type) : CommandObjectRaw(interpreter, name), m_cmd_obj_sp(cmd_obj_sp), m_synchro(synch), m_fetched_help_short(false), m_fetched_help_long(false), m_completion_type(completion_type) { @@ -1168,7 +1171,7 @@ public: GetFlags().Set(scripter->GetFlagsForCommandObject(cmd_obj_sp)); } - ~CommandObjectScriptingObject() override = default; + ~CommandObjectScriptingObjectRaw() override = default; void HandleArgumentCompletion(CompletionRequest &request, @@ -1246,6 +1249,699 @@ private: CompletionType m_completion_type = eNoCompletion; }; + +/// This command implements a lldb parsed scripted command. The command +/// provides a definition of the options and arguments, and a option value +/// setting callback, and then the command's execution function gets passed +/// just the parsed arguments. +/// Note, implementing a command in Python using these base interfaces is a bit +/// of a pain, but it is much easier to export this low level interface, and +/// then make it nicer on the Python side, than to try to do that in a +/// script language neutral way. +/// So I've also added a base class in Python that provides a table-driven +/// way of defining the options and arguments, which automatically fills the +/// option values, making them available as properties in Python. +/// +class CommandObjectScriptingObjectParsed : public CommandObjectParsed { +private: + class CommandOptions : public Options { + public: + CommandOptions(CommandInterpreter &interpreter, + StructuredData::GenericSP cmd_obj_sp) : m_interpreter(interpreter), + m_cmd_obj_sp(cmd_obj_sp) {} + + ~CommandOptions() override = default; + + Status SetOptionValue(uint32_t option_idx, llvm::StringRef option_arg, + ExecutionContext *execution_context) override { + Status error; + ScriptInterpreter *scripter = + m_interpreter.GetDebugger().GetScriptInterpreter(); + if (!scripter) { + error.SetErrorString("No script interpreter for SetOptionValue."); + return error; + } + if (!m_cmd_obj_sp) { + error.SetErrorString("SetOptionValue called with empty cmd_obj."); + return error; + } + if (!m_options_definition_up) { + error.SetErrorString("SetOptionValue called before options definitions " + "were created."); + return error; + } + // Pass the long option, since you aren't actually required to have a + // short_option, and for those options the index or short option character + // aren't meaningful on the python side. + const char * long_option = + m_options_definition_up.get()[option_idx].long_option; + bool success = scripter->SetOptionValueForCommandObject(m_cmd_obj_sp, + execution_context, long_option, option_arg); + if (!success) + error.SetErrorStringWithFormatv("Error setting option: {0} to {1}", + long_option, option_arg); + return error; + } + + void OptionParsingStarting(ExecutionContext *execution_context) override { + ScriptInterpreter *scripter = + m_interpreter.GetDebugger().GetScriptInterpreter(); + if (!scripter || !m_cmd_obj_sp) + return; + + scripter->OptionParsingStartedForCommandObject(m_cmd_obj_sp); + } + + llvm::ArrayRef<OptionDefinition> GetDefinitions() override { + if (!m_options_definition_up) + return {}; + return llvm::ArrayRef(m_options_definition_up.get(), m_num_options); + } + + static Status ParseUsageMaskFromArray(StructuredData::ObjectSP obj_sp, + size_t counter, uint32_t &usage_mask) { + // If the usage entry is not provided, we use LLDB_OPT_SET_ALL. + // If the usage mask is a UINT, the option belongs to that group. + // If the usage mask is a vector of UINT's, the option belongs to all the + // groups listed. + // If a subelement of the vector is a vector of two ints, then the option + // belongs to the inclusive range from the first to the second element. + Status error; + if (!obj_sp) { + usage_mask = LLDB_OPT_SET_ALL; + return error; + } + + usage_mask = 0; + + StructuredData::UnsignedInteger *uint_val = + obj_sp->GetAsUnsignedInteger(); + if (uint_val) { + // If this is an integer, then this specifies a single group: + uint32_t value = uint_val->GetValue(); + if (value == 0) { + error.SetErrorStringWithFormatv( + "0 is not a valid group for option {0}", counter); + return error; + } + usage_mask = (1 << (value - 1)); + return error; + } + // Otherwise it has to be an array: + StructuredData::Array *array_val = obj_sp->GetAsArray(); + if (!array_val) { + error.SetErrorStringWithFormatv( + "required field is not a array for option {0}", counter); + return error; + } + // This is the array ForEach for accumulating a group usage mask from + // an array of string descriptions of groups. + auto groups_accumulator + = [counter, &usage_mask, &error] + (StructuredData::Object *obj) -> bool { + StructuredData::UnsignedInteger *int_val = obj->GetAsUnsignedInteger(); + if (int_val) { + uint32_t value = int_val->GetValue(); + if (value == 0) { + error.SetErrorStringWithFormatv( + "0 is not a valid group for element {0}", counter); + return false; + } + usage_mask |= (1 << (value - 1)); + return true; + } + StructuredData::Array *arr_val = obj->GetAsArray(); + if (!arr_val) { + error.SetErrorStringWithFormatv( + "Group element not an int or array of integers for element {0}", + counter); + return false; + } + size_t num_range_elem = arr_val->GetSize(); + if (num_range_elem != 2) { + error.SetErrorStringWithFormatv( + "Subranges of a group not a start and a stop for element {0}", + counter); + return false; + } + int_val = arr_val->GetItemAtIndex(0)->GetAsUnsignedInteger(); + if (!int_val) { + error.SetErrorStringWithFormatv("Start element of a subrange of a " + "group not unsigned int for element {0}", counter); + return false; + } + uint32_t start = int_val->GetValue(); + int_val = arr_val->GetItemAtIndex(1)->GetAsUnsignedInteger(); + if (!int_val) { + error.SetErrorStringWithFormatv("End element of a subrange of a group" + " not unsigned int for element {0}", counter); + return false; + } + uint32_t end = int_val->GetValue(); + if (start == 0 || end == 0 || start > end) { + error.SetErrorStringWithFormatv("Invalid subrange of a group: {0} - " + "{1} for element {2}", start, end, counter); + return false; + } + for (uint32_t i = start; i <= end; i++) { + usage_mask |= (1 << (i - 1)); + } + return true; + }; + array_val->ForEach(groups_accumulator); + return error; + } + + + Status SetOptionsFromArray(StructuredData::Dictionary &options) { + Status error; + m_num_options = options.GetSize(); + m_options_definition_up.reset(new OptionDefinition[m_num_options]); + // We need to hand out pointers to contents of these vectors; we reserve + // as much as we'll need up front so they don't get freed on resize... + m_usage_container.reserve(m_num_options); + m_enum_storage.reserve(m_num_options); + m_enum_vector.reserve(m_num_options); + + size_t counter = 0; + size_t short_opt_counter = 0; + // This is the Array::ForEach function for adding option elements: + auto add_element = [this, &error, &counter, &short_opt_counter] + (llvm::StringRef long_option, StructuredData::Object *object) -> bool { + StructuredData::Dictionary *opt_dict = object->GetAsDictionary(); + if (!opt_dict) { + error.SetErrorString("Value in options dictionary is not a dictionary"); + return false; + } + OptionDefinition &option_def = m_options_definition_up.get()[counter]; + + // We aren't exposing the validator yet, set it to null + option_def.validator = nullptr; + // We don't require usage masks, so set it to one group by default: + option_def.usage_mask = 1; + + // Now set the fields of the OptionDefinition Array from the dictionary: + // + // Note that I don't check for unknown fields in the option dictionaries + // so a scriptor can add extra elements that are helpful when they go to + // do "set_option_value" + + // Usage Mask: + StructuredData::ObjectSP obj_sp = opt_dict->GetValueForKey("groups"); + if (obj_sp) { + error = ParseUsageMaskFromArray(obj_sp, counter, + option_def.usage_mask); + if (error.Fail()) + return false; + } + + // Required: + option_def.required = false; + obj_sp = opt_dict->GetValueForKey("required"); + if (obj_sp) { + StructuredData::Boolean *boolean_val = obj_sp->GetAsBoolean(); + if (!boolean_val) { + error.SetErrorStringWithFormatv("'required' field is not a boolean " + "for option {0}", counter); + return false; + } + option_def.required = boolean_val->GetValue(); + } + + // Short Option: + int short_option; + obj_sp = opt_dict->GetValueForKey("short_option"); + if (obj_sp) { + // The value is a string, so pull the + llvm::StringRef short_str = obj_sp->GetStringValue(); + if (short_str.empty()) { + error.SetErrorStringWithFormatv("short_option field empty for " + "option {0}", counter); + return false; + } else if (short_str.size() != 1) { + error.SetErrorStringWithFormatv("short_option field has extra " + "characters for option {0}", counter); + return false; + } + short_option = (int) short_str[0]; + } else { + // If the short option is not provided, then we need a unique value + // less than the lowest printable ASCII character. + short_option = short_opt_counter++; + } + option_def.short_option = short_option; + + // Long Option is the key from the outer dict: + if (long_option.empty()) { + error.SetErrorStringWithFormatv("empty long_option for option {0}", + counter); + return false; + } + auto inserted = g_string_storer.insert(long_option.str()); + option_def.long_option = ((*(inserted.first)).data()); + + // Value Type: + obj_sp = opt_dict->GetValueForKey("value_type"); + if (obj_sp) { + StructuredData::UnsignedInteger *uint_val + = obj_sp->GetAsUnsignedInteger(); + if (!uint_val) { + error.SetErrorStringWithFormatv("Value type must be an unsigned " + "integer"); + return false; + } + uint64_t val_type = uint_val->GetValue(); + if (val_type >= eArgTypeLastArg) { + error.SetErrorStringWithFormatv("Value type {0} beyond the " + "CommandArgumentType bounds", val_type); + return false; + } + option_def.argument_type = (CommandArgumentType) val_type; + option_def.option_has_arg = true; + } else { + option_def.argument_type = eArgTypeNone; + option_def.option_has_arg = false; + } + + // Completion Type: + obj_sp = opt_dict->GetValueForKey("completion_type"); + if (obj_sp) { + StructuredData::UnsignedInteger *uint_val = obj_sp->GetAsUnsignedInteger(); + if (!uint_val) { + error.SetErrorStringWithFormatv("Completion type must be an " + "unsigned integer for option {0}", counter); + return false; + } + uint64_t completion_type = uint_val->GetValue(); + if (completion_type > eCustomCompletion) { + error.SetErrorStringWithFormatv("Completion type for option {0} " + "beyond the CompletionType bounds", completion_type); + return false; + } + option_def.completion_type = (CommandArgumentType) completion_type; + } else + option_def.completion_type = eNoCompletion; + + // Usage Text: + std::string usage_text; + obj_sp = opt_dict->GetValueForKey("help"); + if (!obj_sp) { + error.SetErrorStringWithFormatv("required usage missing from option " + "{0}", counter); + return false; + } + llvm::StringRef usage_stref; + usage_stref = obj_sp->GetStringValue(); + if (usage_stref.empty()) { + error.SetErrorStringWithFormatv("empty usage text for option {0}", + counter); + return false; + } + m_usage_container[counter] = usage_stref.str().c_str(); + option_def.usage_text = m_usage_container[counter].data(); + + // Enum Values: + + obj_sp = opt_dict->GetValueForKey("enum_values"); + if (obj_sp) { + StructuredData::Array *array = obj_sp->GetAsArray(); + if (!array) { + error.SetErrorStringWithFormatv("enum values must be an array for " + "option {0}", counter); + return false; + } + size_t num_elem = array->GetSize(); + size_t enum_ctr = 0; + m_enum_storage[counter] = std::vector<EnumValueStorage>(num_elem); + std::vector<EnumValueStorage> &curr_elem = m_enum_storage[counter]; + + // This is the Array::ForEach function for adding enum elements: + // Since there are only two fields to specify the enum, use a simple + // two element array with value first, usage second. + // counter is only used for reporting so I pass it by value here. + auto add_enum = [&enum_ctr, &curr_elem, counter, &error] + (StructuredData::Object *object) -> bool { + StructuredData::Array *enum_arr = object->GetAsArray(); + if (!enum_arr) { + error.SetErrorStringWithFormatv("Enum values for option {0} not " + "an array", counter); + return false; + } + size_t num_enum_elements = enum_arr->GetSize(); + if (num_enum_elements != 2) { + error.SetErrorStringWithFormatv("Wrong number of elements: {0} " + "for enum {1} in option {2}", + num_enum_elements, enum_ctr, counter); + return false; + } + // Enum Value: + StructuredData::ObjectSP obj_sp = enum_arr->GetItemAtIndex(0); + llvm::StringRef val_stref = obj_sp->GetStringValue(); + std::string value_cstr_str = val_stref.str().c_str(); + + // Enum Usage: + obj_sp = enum_arr->GetItemAtIndex(1); + if (!obj_sp) { + error.SetErrorStringWithFormatv("No usage for enum {0} in option " + "{1}", enum_ctr, counter); + return false; + } + llvm::StringRef usage_stref = obj_sp->GetStringValue(); + std::string usage_cstr_str = usage_stref.str().c_str(); + curr_elem[enum_ctr] = EnumValueStorage(value_cstr_str, + usage_cstr_str, enum_ctr); + + enum_ctr++; + return true; + }; // end of add_enum + + array->ForEach(add_enum); + if (!error.Success()) + return false; + // We have to have a vector of elements to set in the options, make + // that here: + for (auto &elem : curr_elem) + m_enum_vector[counter].emplace_back(elem.element); + + option_def.enum_values = llvm::ArrayRef(m_enum_vector[counter]); + } + counter++; + return true; + }; // end of add_element + + options.ForEach(add_element); + return error; + } + + private: + struct EnumValueStorage { + EnumValueStorage() { + element.string_value = "value not set"; + element.usage = "usage not set"; + element.value = 0; + } + + EnumValueStorage(std::string in_str_val, std::string in_usage, + size_t in_value) : value(std::move(in_str_val)), usage(std::move(in_usage)) { + SetElement(in_value); + } + + EnumValueStorage(const EnumValueStorage &in) : value(in.value), + usage(in.usage) { + SetElement(in.element.value); + } + + EnumValueStorage &operator=(const EnumValueStorage &in) { + value = in.value; + usage = in.usage; + SetElement(in.element.value); + return *this; + } + + void SetElement(size_t in_value) { + element.value = in_value; + element.string_value = value.data(); + element.usage = usage.data(); + } + + std::string value; + std::string usage; + OptionEnumValueElement element; + }; + // We have to provide char * values for the long option, usage and enum + // values, that's what the option definitions hold. + // The long option strings are quite likely to be reused in other added + // commands, so those are stored in a global set: g_string_storer. + // But the usages are much less likely to be reused, so those are stored in + // a vector in the command instance. It gets resized to the correct size + // and then filled with null-terminated strings in the std::string, so the + // are valid C-strings that won't move around. + // The enum values and descriptions are treated similarly - these aren't + // all that common so it's not worth the effort to dedup them. + size_t m_num_options = 0; + std::unique_ptr<OptionDefinition> m_options_definition_up; + std::vector<std::vector<EnumValueStorage>> m_enum_storage; + std::vector<std::vector<OptionEnumValueElement>> m_enum_vector; + std::vector<std::string> m_usage_container; + CommandInterpreter &m_interpreter; + StructuredData::GenericSP m_cmd_obj_sp; + static std::unordered_set<std::string> g_string_storer; + }; + +public: + static CommandObjectSP Create(CommandInterpreter &interpreter, + std::string name, + StructuredData::GenericSP cmd_obj_sp, + ScriptedCommandSynchronicity synch, + CommandReturnObject &result) { + CommandObjectSP new_cmd_sp(new CommandObjectScriptingObjectParsed( + interpreter, name, cmd_obj_sp, synch)); + + CommandObjectScriptingObjectParsed *parsed_cmd + = static_cast<CommandObjectScriptingObjectParsed *>(new_cmd_sp.get()); + // Now check all the failure modes, and report if found. + Status opt_error = parsed_cmd->GetOptionsError(); + Status arg_error = parsed_cmd->GetArgsError(); + + if (opt_error.Fail()) + result.AppendErrorWithFormat("failed to parse option definitions: %s", + opt_error.AsCString()); + if (arg_error.Fail()) + result.AppendErrorWithFormat("%sfailed to parse argument definitions: %s", + opt_error.Fail() ? ", also " : "", + arg_error.AsCString()); + + if (!result.Succeeded()) + return {}; + + return new_cmd_sp; + } + + CommandObjectScriptingObjectParsed(CommandInterpreter &interpreter, + std::string name, + StructuredData::GenericSP cmd_obj_sp, + ScriptedCommandSynchronicity synch) + : CommandObjectParsed(interpreter, name.c_str()), + m_cmd_obj_sp(cmd_obj_sp), m_synchro(synch), + m_options(interpreter, cmd_obj_sp), m_fetched_help_short(false), + m_fetched_help_long(false) { + StreamString stream; + ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter(); + if (!scripter) { + m_options_error.SetErrorString("No script interpreter"); + return; + } + + // Set the flags: + GetFlags().Set(scripter->GetFlagsForCommandObject(cmd_obj_sp)); + + // Now set up the options definitions from the options: + StructuredData::ObjectSP options_object_sp + = scripter->GetOptionsForCommandObject(cmd_obj_sp); + // It's okay not to have an options dict. + if (options_object_sp) { + // The options come as a dictionary of dictionaries. The key of the + // outer dict is the long option name (since that's required). The + // value holds all the other option specification bits. + StructuredData::Dictionary *options_dict + = options_object_sp->GetAsDictionary(); + // but if it exists, it has to be an array. + if (options_dict) { + m_options_error = m_options.SetOptionsFromArray(*(options_dict)); + // If we got an error don't bother with the arguments... + if (m_options_error.Fail()) + return; + } else { + m_options_error.SetErrorString("Options array not an array"); + return; + } + } + // Then fetch the args. Since the arguments can have usage masks you need + // an array of arrays. + StructuredData::ObjectSP args_object_sp + = scripter->GetArgumentsForCommandObject(cmd_obj_sp); + if (args_object_sp) { + StructuredData::Array *args_array = args_object_sp->GetAsArray(); + if (!args_array) { + m_args_error.SetErrorString("Argument specification is not an array"); + return; + } + size_t counter = 0; + + // This is the Array::ForEach function that handles the + // CommandArgumentEntry arrays one by one: + auto arg_array_adder = [this, &counter] (StructuredData::Object *object) + -> bool { + // This is the Array::ForEach function to add argument entries: + CommandArgumentEntry this_entry; + size_t elem_counter = 0; + auto args_adder = [this, counter, &elem_counter, &this_entry] + (StructuredData::Object *object) -> bool { + // The arguments definition has three fields, the argument type, the + // repeat and the usage mask. + CommandArgumentType arg_type = eArgTypeNone; + ArgumentRepetitionType arg_repetition = eArgRepeatOptional; + uint32_t arg_opt_set_association; + + auto report_error = [this, elem_counter, counter] + (const char *err_txt) -> bool { + m_args_error.SetErrorStringWithFormatv("Element {0} of arguments " + "list element {1}: %s.", elem_counter, counter, err_txt); + return false; + }; + + StructuredData::Dictionary *arg_dict = object->GetAsDictionary(); + if (!arg_dict) { + report_error("is not a dictionary."); + return false; + } + // Argument Type: + StructuredData::ObjectSP obj_sp + = arg_dict->GetValueForKey("arg_type"); + if (obj_sp) { + StructuredData::UnsignedInteger *uint_val + = obj_sp->GetAsUnsignedInteger(); + if (!uint_val) { + report_error("value type must be an unsigned integer"); + return false; + } + uint64_t arg_type_int = uint_val->GetValue(); + if (arg_type_int >= eArgTypeLastArg) { + report_error("value type beyond ArgumentRepetitionType bounds"); + return false; + } + arg_type = (CommandArgumentType) arg_type_int; + } + // Repeat Value: + obj_sp = arg_dict->GetValueForKey("repeat"); + std::optional<ArgumentRepetitionType> repeat; + if (obj_sp) { + llvm::StringRef repeat_str = obj_sp->GetStringValue(); + if (repeat_str.empty()) { + report_error("repeat value is empty"); + return false; + } + repeat = ArgRepetitionFromString(repeat_str); + if (!repeat) { + report_error("invalid repeat value"); + return false; + } + arg_repetition = *repeat; + } + + // Usage Mask: + obj_sp = arg_dict->GetValueForKey("groups"); + m_args_error = CommandOptions::ParseUsageMaskFromArray(obj_sp, + counter, arg_opt_set_association); + this_entry.emplace_back(arg_type, arg_repetition, + arg_opt_set_association); + elem_counter++; + return true; + }; + StructuredData::Array *args_array = object->GetAsArray(); + if (!args_array) { + m_args_error.SetErrorStringWithFormatv("Argument definition element " + "{0} is not an array", counter); + } + + args_array->ForEach(args_adder); + if (m_args_error.Fail()) + return false; + if (this_entry.empty()) { + m_args_error.SetErrorStringWithFormatv("Argument definition element " + "{0} is empty", counter); + return false; + } + m_arguments.push_back(this_entry); + counter++; + return true; + }; // end of arg_array_adder + // Here we actually parse the args definition: + args_array->ForEach(arg_array_adder); + } + } + + ~CommandObjectScriptingObjectParsed() override = default; + + Status GetOptionsError() { return m_options_error; } + Status GetArgsError() { return m_args_error; } + bool WantsCompletion() override { return true; } + + bool IsRemovable() const override { return true; } + + ScriptedCommandSynchronicity GetSynchronicity() { return m_synchro; } + + llvm::StringRef GetHelp() override { + if (m_fetched_help_short) + return CommandObjectParsed::GetHelp(); + ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter(); + if (!scripter) + return CommandObjectParsed::GetHelp(); + std::string docstring; + m_fetched_help_short = + scripter->GetShortHelpForCommandObject(m_cmd_obj_sp, docstring); + if (!docstring.empty()) + SetHelp(docstring); + + return CommandObjectParsed::GetHelp(); + } + + llvm::StringRef GetHelpLong() override { + if (m_fetched_help_long) + return CommandObjectParsed::GetHelpLong(); + + ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter(); + if (!scripter) + return CommandObjectParsed::GetHelpLong(); + + std::string docstring; + m_fetched_help_long = + scripter->GetLongHelpForCommandObject(m_cmd_obj_sp, docstring); + if (!docstring.empty()) + SetHelpLong(docstring); + return CommandObjectParsed::GetHelpLong(); + } + + Options *GetOptions() override { return &m_options; } + + +protected: + void DoExecute(Args &args, + CommandReturnObject &result) override { + ScriptInterpreter *scripter = GetDebugger().GetScriptInterpreter(); + + Status error; + + result.SetStatus(eReturnStatusInvalid); + + if (!scripter || + !scripter->RunScriptBasedParsedCommand(m_cmd_obj_sp, args, + m_synchro, result, error, m_exe_ctx)) { + result.AppendError(error.AsCString()); + } else { + // Don't change the status if the command already set it... + if (result.GetStatus() == eReturnStatusInvalid) { + if (result.GetOutputData().empty()) + result.SetStatus(eReturnStatusSuccessFinishNoResult); + else + result.SetStatus(eReturnStatusSuccessFinishResult); + } + } + } + +private: + StructuredData::GenericSP m_cmd_obj_sp; + ScriptedCommandSynchronicity m_synchro; + CommandOptions m_options; + Status m_options_error; + Status m_args_error; + bool m_fetched_help_short : 1; + bool m_fetched_help_long : 1; +}; + +std::unordered_set<std::string> + CommandObjectScriptingObjectParsed::CommandOptions::g_string_storer; + // CommandObjectCommandsScriptImport #define LLDB_OPTIONS_script_import #include "CommandOptions.inc" @@ -1439,6 +2135,9 @@ protected: case 'o': m_overwrite_lazy = eLazyBoolYes; break; + case 'p': + m_parsed_command = true; + break; case 's': m_synchronicity = (ScriptedCommandSynchronicity)OptionArgParser::ToOptionEnum( @@ -1474,6 +2173,7 @@ protected: m_completion_type = eNoCompletion; m_overwrite_lazy = eLazyBoolCalculate; m_synchronicity = eScriptedCommandSynchronicitySynchronous; + m_parsed_command = false; } llvm::ArrayRef<OptionDefinition> GetDefinitions() override { @@ -1489,6 +2189,7 @@ protected: ScriptedCommandSynchronicity m_synchronicity = eScriptedCommandSynchronicitySynchronous; CompletionType m_completion_type = eNoCompletion; + bool m_parsed_command = false; }; void IOHandlerActivated(IOHandler &io_handler, bool interactive) override { @@ -1628,10 +2329,16 @@ protected: "'{0}'", m_options.m_class_name); return; } - - new_cmd_sp.reset(new CommandObjectScriptingObject( - m_interpreter, m_cmd_name, cmd_obj_sp, m_synchronicity, - m_completion_type)); + + if (m_options.m_parsed_command) { + new_cmd_sp = CommandObjectScriptingObjectParsed::Create(m_interpreter, + m_cmd_name, cmd_obj_sp, m_synchronicity, result); + if (!result.Succeeded()) + return; + } else + new_cmd_sp.reset(new CommandObjectScriptingObjectRaw( + m_interpreter, m_cmd_name, cmd_obj_sp, m_synchronicity, + m_completion_type)); } // Assume we're going to succeed... diff --git a/lldb/source/Commands/Options.td b/lldb/source/Commands/Options.td index a87f457..dd732e3 100644 --- a/lldb/source/Commands/Options.td +++ b/lldb/source/Commands/Options.td @@ -805,19 +805,25 @@ let Command = "script add" in { def script_add_function : Option<"function", "f">, Group<1>, Arg<"PythonFunction">, Desc<"Name of the Python function to bind to this command name.">; - def script_add_class : Option<"class", "c">, Group<2>, Arg<"PythonClass">, - Desc<"Name of the Python class to bind to this command name.">; + def script_add_class : Option<"class", "c">, Groups<[2,3]>, + Arg<"PythonClass">, + Desc<"Name of the Python class to bind to this command name.">; def script_add_help : Option<"help", "h">, Group<1>, Arg<"HelpText">, - Desc<"The help text to display for this command.">; - def script_add_overwrite : Option<"overwrite", "o">, Groups<[1,2]>, - Desc<"Overwrite an existing command at this node.">; + Desc<"The help text to display for this command.">; + def script_add_overwrite : Option<"overwrite", "o">, + Desc<"Overwrite an existing command at this node.">; def script_add_synchronicity : Option<"synchronicity", "s">, EnumArg<"ScriptedCommandSynchronicity">, Desc<"Set the synchronicity of this command's executions with regard to " "LLDB event system.">; - def completion_type : Option<"completion-type", "C">, - EnumArg<"CompletionType">, - Desc<"Specify which completion type the command should use - if none is specified, the command won't use auto-completion.">; + def script_add_completion_type : Option<"completion-type", "C">, + Groups<[1,2]>, EnumArg<"CompletionType">, + Desc<"Specify which completion type the command should use - if none is " + "specified, the command won't use auto-completion.">; + def script_add_parsed_command : Option<"parsed", "p">, Group<3>, + Desc<"Make a parsed command. The command class will provide the command " + "definition by implementing get_options and get_arguments.">; + } let Command = "container add" in { diff --git a/lldb/source/Interpreter/CommandObject.cpp b/lldb/source/Interpreter/CommandObject.cpp index 6324c7e..6ed0fd1 100644 --- a/lldb/source/Interpreter/CommandObject.cpp +++ b/lldb/source/Interpreter/CommandObject.cpp @@ -447,6 +447,23 @@ bool CommandObject::IsPairType(ArgumentRepetitionType arg_repeat_type) { (arg_repeat_type == eArgRepeatPairRangeOptional); } +std::optional<ArgumentRepetitionType> +CommandObject::ArgRepetitionFromString(llvm::StringRef string) { + return llvm::StringSwitch<ArgumentRepetitionType>(string) + .Case("plain", eArgRepeatPlain) + .Case("optional", eArgRepeatOptional) + .Case("plus", eArgRepeatPlus) + .Case("star", eArgRepeatStar) + .Case("range", eArgRepeatRange) + .Case("pair-plain", eArgRepeatPairPlain) + .Case("pair-optional", eArgRepeatPairOptional) + .Case("pair-plus", eArgRepeatPairPlus) + .Case("pair-star", eArgRepeatPairStar) + .Case("pair-range", eArgRepeatPairRange) + .Case("pair-range-optional", eArgRepeatPairRangeOptional) + .Default({}); +} + static CommandObject::CommandArgumentEntry OptSetFiltered(uint32_t opt_set_mask, CommandObject::CommandArgumentEntry &cmd_arg_entry) { diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h index 82eee76..88c1bb7 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h @@ -194,6 +194,8 @@ template <typename T, char F> struct PassthroughFormat { }; template <> struct PythonFormat<char *> : PassthroughFormat<char *, 's'> {}; +template <> struct PythonFormat<const char *> : + PassthroughFormat<const char *, 's'> {}; template <> struct PythonFormat<char> : PassthroughFormat<char, 'b'> {}; template <> struct PythonFormat<unsigned char> : PassthroughFormat<unsigned char, 'B'> {}; diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h index 7cdd557..c1a11b9 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h @@ -32,6 +32,7 @@ class SBStream; class SBStructuredData; class SBFileSpec; class SBModuleSpec; +class SBStringList; } // namespace lldb namespace lldb_private { @@ -212,6 +213,12 @@ public: lldb::DebuggerSP debugger, const char *args, lldb_private::CommandReturnObject &cmd_retobj, lldb::ExecutionContextRefSP exe_ctx_ref_sp); + static bool + LLDBSwigPythonCallParsedCommandObject(PyObject *implementor, + lldb::DebuggerSP debugger, + StructuredDataImpl &args_impl, + lldb_private::CommandReturnObject &cmd_retobj, + lldb::ExecutionContextRefSP exe_ctx_ref_sp); static bool LLDBSwigPythonCallModuleInit(const char *python_module_name, const char *session_dictionary_name, diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp index ef7a2c1..dadcde6 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -24,6 +24,7 @@ #include "ScriptInterpreterPythonImpl.h" #include "lldb/API/SBError.h" +#include "lldb/API/SBExecutionContext.h" #include "lldb/API/SBFrame.h" #include "lldb/API/SBValue.h" #include "lldb/Breakpoint/StoppointCallbackContext.h" @@ -531,7 +532,6 @@ void ScriptInterpreterPythonImpl::IOHandlerInputComplete(IOHandler &io_handler, break; data_up->user_source.SplitIntoLines(data); - StructuredData::ObjectSP empty_args_sp; if (GenerateBreakpointCommandCallbackData(data_up->user_source, data_up->script_source, /*has_extra_args=*/false, @@ -2766,6 +2766,58 @@ bool ScriptInterpreterPythonImpl::RunScriptBasedCommand( return ret_val; } +bool ScriptInterpreterPythonImpl::RunScriptBasedParsedCommand( + StructuredData::GenericSP impl_obj_sp, Args &args, + ScriptedCommandSynchronicity synchronicity, + lldb_private::CommandReturnObject &cmd_retobj, Status &error, + const lldb_private::ExecutionContext &exe_ctx) { + if (!impl_obj_sp || !impl_obj_sp->IsValid()) { + error.SetErrorString("no function to execute"); + return false; + } + + lldb::DebuggerSP debugger_sp = m_debugger.shared_from_this(); + lldb::ExecutionContextRefSP exe_ctx_ref_sp(new ExecutionContextRef(exe_ctx)); + + if (!debugger_sp.get()) { + error.SetErrorString("invalid Debugger pointer"); + return false; + } + + bool ret_val = false; + + std::string err_msg; + + { + Locker py_lock(this, + Locker::AcquireLock | Locker::InitSession | + (cmd_retobj.GetInteractive() ? 0 : Locker::NoSTDIN), + Locker::FreeLock | Locker::TearDownSession); + + SynchronicityHandler synch_handler(debugger_sp, synchronicity); + + StructuredData::ArraySP args_arr_sp(new StructuredData::Array()); + + for (const Args::ArgEntry &entry : args) { + args_arr_sp->AddStringItem(entry.ref()); + } + StructuredDataImpl args_impl(args_arr_sp); + + ret_val = SWIGBridge::LLDBSwigPythonCallParsedCommandObject( + static_cast<PyObject *>(impl_obj_sp->GetValue()), debugger_sp, + args_impl, cmd_retobj, exe_ctx_ref_sp); + } + + if (!ret_val) + error.SetErrorString("unable to execute script function"); + else if (cmd_retobj.GetStatus() == eReturnStatusFailed) + return false; + + error.Clear(); + return ret_val; +} + + /// In Python, a special attribute __doc__ contains the docstring for an object /// (function, method, class, ...) if any is defined Otherwise, the attribute's /// value is None. @@ -2884,6 +2936,205 @@ uint32_t ScriptInterpreterPythonImpl::GetFlagsForCommandObject( return result; } +StructuredData::ObjectSP +ScriptInterpreterPythonImpl::GetOptionsForCommandObject( + StructuredData::GenericSP cmd_obj_sp) { + StructuredData::ObjectSP result = {}; + + Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock); + + static char callee_name[] = "get_options_definition"; + + if (!cmd_obj_sp) + return result; + + PythonObject implementor(PyRefType::Borrowed, + (PyObject *)cmd_obj_sp->GetValue()); + + if (!implementor.IsAllocated()) + return result; + + PythonObject pmeth(PyRefType::Owned, + PyObject_GetAttrString(implementor.get(), callee_name)); + + if (PyErr_Occurred()) + PyErr_Clear(); + + if (!pmeth.IsAllocated()) + return result; + + if (PyCallable_Check(pmeth.get()) == 0) { + if (PyErr_Occurred()) + PyErr_Clear(); + return result; + } + + if (PyErr_Occurred()) + PyErr_Clear(); + + PythonDictionary py_return = unwrapOrSetPythonException( + As<PythonDictionary>(implementor.CallMethod(callee_name))); + + // if it fails, print the error but otherwise go on + if (PyErr_Occurred()) { + PyErr_Print(); + PyErr_Clear(); + return {}; + } + return py_return.CreateStructuredObject(); +} + +StructuredData::ObjectSP +ScriptInterpreterPythonImpl::GetArgumentsForCommandObject( + StructuredData::GenericSP cmd_obj_sp) { + StructuredData::ObjectSP result = {}; + + Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock); + + static char callee_name[] = "get_args_definition"; + + if (!cmd_obj_sp) + return result; + + PythonObject implementor(PyRefType::Borrowed, + (PyObject *)cmd_obj_sp->GetValue()); + + if (!implementor.IsAllocated()) + return result; + + PythonObject pmeth(PyRefType::Owned, + PyObject_GetAttrString(implementor.get(), callee_name)); + + if (PyErr_Occurred()) + PyErr_Clear(); + + if (!pmeth.IsAllocated()) + return result; + + if (PyCallable_Check(pmeth.get()) == 0) { + if (PyErr_Occurred()) + PyErr_Clear(); + return result; + } + + if (PyErr_Occurred()) + PyErr_Clear(); + + PythonList py_return = unwrapOrSetPythonException( + As<PythonList>(implementor.CallMethod(callee_name))); + + // if it fails, print the error but otherwise go on + if (PyErr_Occurred()) { + PyErr_Print(); + PyErr_Clear(); + return {}; + } + return py_return.CreateStructuredObject(); +} + +void +ScriptInterpreterPythonImpl::OptionParsingStartedForCommandObject( + StructuredData::GenericSP cmd_obj_sp) { + + Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock); + + static char callee_name[] = "option_parsing_started"; + + if (!cmd_obj_sp) + return ; + + PythonObject implementor(PyRefType::Borrowed, + (PyObject *)cmd_obj_sp->GetValue()); + + if (!implementor.IsAllocated()) + return; + + PythonObject pmeth(PyRefType::Owned, + PyObject_GetAttrString(implementor.get(), callee_name)); + + if (PyErr_Occurred()) + PyErr_Clear(); + + if (!pmeth.IsAllocated()) + return; + + if (PyCallable_Check(pmeth.get()) == 0) { + if (PyErr_Occurred()) + PyErr_Clear(); + return; + } + + if (PyErr_Occurred()) + PyErr_Clear(); + + // option_parsing_starting doesn't return anything, ignore anything but + // python errors. + unwrapOrSetPythonException( + As<bool>(implementor.CallMethod(callee_name))); + + // if it fails, print the error but otherwise go on + if (PyErr_Occurred()) { + PyErr_Print(); + PyErr_Clear(); + return; + } +} + +bool +ScriptInterpreterPythonImpl::SetOptionValueForCommandObject( + StructuredData::GenericSP cmd_obj_sp, ExecutionContext *exe_ctx, + llvm::StringRef long_option, llvm::StringRef value) { + StructuredData::ObjectSP result = {}; + + Locker py_lock(this, Locker::AcquireLock | Locker::NoSTDIN, Locker::FreeLock); + + static char callee_name[] = "set_option_value"; + + if (!cmd_obj_sp) + return false; + + PythonObject implementor(PyRefType::Borrowed, + (PyObject *)cmd_obj_sp->GetValue()); + + if (!implementor.IsAllocated()) + return false; + + PythonObject pmeth(PyRefType::Owned, + PyObject_GetAttrString(implementor.get(), callee_name)); + + if (PyErr_Occurred()) + PyErr_Clear(); + + if (!pmeth.IsAllocated()) + return false; + + if (PyCallable_Check(pmeth.get()) == 0) { + if (PyErr_Occurred()) + PyErr_Clear(); + return false; + } + + if (PyErr_Occurred()) + PyErr_Clear(); + + lldb::ExecutionContextRefSP exe_ctx_ref_sp; + if (exe_ctx) + exe_ctx_ref_sp.reset(new ExecutionContextRef(exe_ctx)); + PythonObject ctx_ref_obj = SWIGBridge::ToSWIGWrapper(exe_ctx_ref_sp); + + bool py_return = unwrapOrSetPythonException( + As<bool>(implementor.CallMethod(callee_name, ctx_ref_obj, long_option.str().c_str(), + value.str().c_str()))); + + // if it fails, print the error but otherwise go on + if (PyErr_Occurred()) { + PyErr_Print(); + PyErr_Clear(); + return false; + } + return py_return; +} + bool ScriptInterpreterPythonImpl::GetLongHelpForCommandObject( StructuredData::GenericSP cmd_obj_sp, std::string &dest) { dest.clear(); diff --git a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h index a3349981..fcd21df 100644 --- a/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ b/lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h @@ -182,6 +182,13 @@ public: lldb_private::CommandReturnObject &cmd_retobj, Status &error, const lldb_private::ExecutionContext &exe_ctx) override; + virtual bool RunScriptBasedParsedCommand( + StructuredData::GenericSP impl_obj_sp, Args& args, + ScriptedCommandSynchronicity synchronicity, + lldb_private::CommandReturnObject &cmd_retobj, Status &error, + const lldb_private::ExecutionContext &exe_ctx) override; + + Status GenerateFunction(const char *signature, const StringList &input, bool is_callback) override; @@ -212,6 +219,20 @@ public: bool GetLongHelpForCommandObject(StructuredData::GenericSP cmd_obj_sp, std::string &dest) override; + + StructuredData::ObjectSP + GetOptionsForCommandObject(StructuredData::GenericSP cmd_obj_sp) override; + + StructuredData::ObjectSP + GetArgumentsForCommandObject(StructuredData::GenericSP cmd_obj_sp) override; + + bool SetOptionValueForCommandObject(StructuredData::GenericSP cmd_obj_sp, + ExecutionContext *exe_ctx, + llvm::StringRef long_option, + llvm::StringRef value) override; + + void OptionParsingStartedForCommandObject( + StructuredData::GenericSP cmd_obj_sp) override; bool CheckObjectExists(const char *name) override { if (!name || !name[0]) diff --git a/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py b/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py new file mode 100644 index 0000000..7dba9c6 --- /dev/null +++ b/lldb/test/API/commands/command/script/add/TestAddParsedCommand.py @@ -0,0 +1,146 @@ +""" +Test option and argument definitions in parsed script commands +""" + + +import sys +import os +import lldb +from lldbsuite.test.decorators import * +from lldbsuite.test.lldbtest import * + + +class ParsedCommandTestCase(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def test(self): + self.pycmd_tests() + + def check_help_options(self, cmd_name, opt_list, substrs = []): + """ + Pass the command name in cmd_name and a vector of the short option, type & long option. + This will append the checks for all the options and test "help command". + Any strings already in substrs will also be checked. + Any element in opt list that begin with "+" will be added to the checked strings as is. + """ + for elem in opt_list: + if elem[0] == "+": + substrs.append(elem[1:]) + else: + (short_opt, type, long_opt) = elem + substrs.append(f"-{short_opt} <{type}> ( --{long_opt} <{type}> )") + print(f"Opt Vec\n{substrs}") + self.expect("help " + cmd_name, substrs = substrs) + + def pycmd_tests(self): + source_dir = self.getSourceDir() + test_file_path = os.path.join(source_dir, "test_commands.py") + self.runCmd("command script import " + test_file_path) + self.expect("help", substrs = ["no-args", "one-arg-no-opt", "two-args"]) + + # Test that we did indeed add these commands as user commands: + + # This is the function to remove the custom commands in order to have a + # clean slate for the next test case. + def cleanup(): + self.runCmd("command script delete no-args one-arg-no-opt two-args", check=False) + + # Execute the cleanup function during test case tear down. + self.addTearDownHook(cleanup) + + # First test the no arguments command. Make sure the help is right: + no_arg_opts = [["b", "boolean", "bool-arg"], + "+a boolean arg, defaults to True", + ["d", "filename", "disk-file-name"], + "+An on disk filename", + ["e", "none", "enum-option"], + "+An enum, doesn't actually do anything", + "+Values: foo | bar | baz", + ["l", "linenum", "line-num"], + "+A line number", + ["s", "shlib-name", "shlib-name"], + "+A shared library name"] + substrs = ["Example command for use in debugging", + "Syntax: no-args <cmd-options>"] + + self.check_help_options("no-args", no_arg_opts, substrs) + + # Make sure the command doesn't accept arguments: + self.expect("no-args an-arg", substrs=["'no-args' doesn't take any arguments."], + error=True) + + # Try setting the bool with the wrong value: + self.expect("no-args -b Something", + substrs=["Error setting option: bool-arg to Something"], + error=True) + # Try setting the enum to an illegal value as well: + self.expect("no-args --enum-option Something", + substrs=["error: Error setting option: enum-option to Something"], + error=True) + + # Check some of the command groups: + self.expect("no-args -b true -s Something -l 10", + substrs=["error: invalid combination of options for the given command"], + error=True) + + # Now set the bool arg correctly, note only the first option was set: + self.expect("no-args -b true", substrs=["bool-arg (set: True): True", + "shlib-name (set: False):", + "disk-file-name (set: False):", + "line-num (set: False):", + "enum-option (set: False):"]) + + # Now set the enum arg correctly, note only the first option was set: + self.expect("no-args -e foo", substrs=["bool-arg (set: False):", + "shlib-name (set: False):", + "disk-file-name (set: False):", + "line-num (set: False):", + "enum-option (set: True): foo"]) + # Try a pair together: + self.expect("no-args -b false -s Something", substrs=["bool-arg (set: True): False", + "shlib-name (set: True): Something", + "disk-file-name (set: False):", + "line-num (set: False):", + "enum-option (set: False):"]) + + # Next try some completion tests: + + interp = self.dbg.GetCommandInterpreter() + matches = lldb.SBStringList() + descriptions = lldb.SBStringList() + + # First try an enum completion: + num_completions = interp.HandleCompletionWithDescriptions("no-args -e f", 12, 0, + 1000, matches, descriptions) + self.assertEqual(num_completions, 1, "Only one completion for foo") + self.assertEqual(matches.GetSize(), 2, "The first element is the complete additional text") + self.assertEqual(matches.GetStringAtIndex(0), "oo ", "And we got the right extra characters") + self.assertEqual(matches.GetStringAtIndex(1), "foo", "And we got the right match") + self.assertEqual(descriptions.GetSize(), 2, "descriptions matche the return length") + # FIXME: we don't return descriptions for enum elements + #self.assertEqual(descriptions.GetStringAtIndex(1), "does foo things", "And we got the right description") + + # Now try an internal completer, the on disk file one is handy: + partial_name = os.path.join(source_dir, "test_") + cmd_str = f"no-args -d '{partial_name}'" + + matches.Clear() + descriptions.Clear() + num_completions = interp.HandleCompletionWithDescriptions(cmd_str, len(cmd_str) - 1, 0, + 1000, matches, descriptions) + print(f"First: {matches.GetStringAtIndex(0)}\nSecond: {matches.GetStringAtIndex(1)}\nThird: {matches.GetStringAtIndex(2)}") + self.assertEqual(num_completions, 1, "Only one completion for source file") + self.assertEqual(matches.GetSize(), 2, "The first element is the complete line") + self.assertEqual(matches.GetStringAtIndex(0), "commands.py' ", "And we got the right extra characters") + self.assertEqual(matches.GetStringAtIndex(1), test_file_path, "And we got the right match") + self.assertEqual(descriptions.GetSize(), 2, "descriptions match the return length") + # FIXME: we don't return descriptions for enum elements + #self.assertEqual(descriptions.GetStringAtIndex(1), "does foo things", "And we got the right description") + + # Try a command with arguments. + # FIXME: It should be enough to define an argument and it's type to get the completer + # wired up for that argument type if it is a known type. But that isn't wired up in the + # command parser yet, so I don't have any tests for that. We also don't currently check + # that the arguments passed match the argument specifications, so here I just pass a couple + # sets of arguments and make sure we get back what we put in: + self.expect("two-args 'First Argument' 'Second Argument'", substrs=["0: First Argument", "1: Second Argument"]) diff --git a/lldb/test/API/commands/command/script/add/test_commands.py b/lldb/test/API/commands/command/script/add/test_commands.py new file mode 100644 index 0000000..801d588 --- /dev/null +++ b/lldb/test/API/commands/command/script/add/test_commands.py @@ -0,0 +1,174 @@ +""" +Test defining commands using the lldb command definitions +""" +import inspect +import sys +import lldb +from lldb.plugins.parsed_cmd import ParsedCommand + +class ReportingCmd(ParsedCommand): + def __init__(self, debugger, unused): + super().__init__(debugger, unused) + + def __call__(self, debugger, args_array, exe_ctx, result): + opt_def = self.get_options_definition() + if len(opt_def): + result.AppendMessage("Options:\n") + for long_option, elem in opt_def.items(): + dest = elem["dest"] + result.AppendMessage(f"{long_option} (set: {elem['_value_set']}): {object.__getattribute__(self.ov_parser, dest)}\n") + else: + result.AppendMessage("No options\n") + + num_args = args_array.GetSize() + if num_args > 0: + result.AppendMessage(f"{num_args} arguments:") + for idx in range(0,num_args): + result.AppendMessage(f"{idx}: {args_array.GetItemAtIndex(idx).GetStringValue(10000)}\n") + +class NoArgsCommand(ReportingCmd): + program = "no-args" + + def __init__(self, debugger, unused): + super().__init__(debugger, unused) + + @classmethod + def register_lldb_command(cls, debugger, module_name): + ParsedCommand.do_register_cmd(cls, debugger, module_name) + + def setup_command_definition(self): + self.ov_parser.add_option( + "b", + "bool-arg", + "a boolean arg, defaults to True", + value_type = lldb.eArgTypeBoolean, + groups = [1,2], + dest = "bool_arg", + default = True + ) + + self.ov_parser.add_option( + "s", + "shlib-name", + "A shared library name.", + value_type=lldb.eArgTypeShlibName, + groups = [1, [3,4]], + dest = "shlib_name", + default = None + ) + + self.ov_parser.add_option( + "d", + "disk-file-name", + "An on disk filename", + value_type = lldb.eArgTypeFilename, + dest = "disk_file_name", + default = None + ) + + self.ov_parser.add_option( + "l", + "line-num", + "A line number", + value_type = lldb.eArgTypeLineNum, + groups = 3, + dest = "line_num", + default = 0 + ) + + self.ov_parser.add_option( + "e", + "enum-option", + "An enum, doesn't actually do anything", + enum_values = [["foo", "does foo things"], + ["bar", "does bar things"], + ["baz", "does baz things"]], + groups = 4, + dest = "enum_option", + default = "foo" + ) + + def get_short_help(self): + return "Example command for use in debugging" + + def get_long_help(self): + return self.help_string + +class OneArgCommandNoOptions(ReportingCmd): + program = "one-arg-no-opt" + + def __init__(self, debugger, unused): + super().__init__(debugger, unused) + + @classmethod + def register_lldb_command(cls, debugger, module_name): + ParsedCommand.do_register_cmd(cls, debugger, module_name) + + def setup_command_definition(self): + self.ov_parser.add_argument_set([self.ov_parser.make_argument_element(lldb.eArgTypeSourceFile, "plain")]) + + def get_short_help(self): + return "Example command for use in debugging" + + def get_long_help(self): + return self.help_string + +class TwoArgGroupsCommand(ReportingCmd): + program = "two-args" + + def __init__(self, debugger, unused): + super().__init__(debugger, unused) + + @classmethod + def register_lldb_command(cls, debugger, module_name): + ParsedCommand.do_register_cmd(cls, debugger, module_name) + + def setup_command_definition(self): + self.ov_parser.add_option( + "l", + "language", + "language defaults to None", + value_type = lldb.eArgTypeLanguage, + groups = [1,2], + dest = "language", + default = None + ) + + self.ov_parser.add_option( + "c", + "log-channel", + "log channel - defaults to lldb", + value_type=lldb.eArgTypeLogChannel, + groups = [1, 3], + dest = "log_channel", + default = "lldb" + ) + + self.ov_parser.add_option( + "p", + "process-name", + "A process name, defaults to None", + value_type = lldb.eArgTypeProcessName, + dest = "proc_name", + default = None + ) + + self.ov_parser.add_argument_set([self.ov_parser.make_argument_element(lldb.eArgTypeClassName, "plain", [1,2]), + self.ov_parser.make_argument_element(lldb.eArgTypeOffset, "optional", [1,2])]) + + self.ov_parser.add_argument_set([self.ov_parser.make_argument_element(lldb.eArgTypePythonClass, "plain", [3,4]), + self.ov_parser.make_argument_element(lldb.eArgTypePid, "optional", [3,4])]) + + def get_short_help(self): + return "Example command for use in debugging" + + def get_long_help(self): + return self.help_string + +def __lldb_init_module(debugger, dict): + # Register all classes that have a register_lldb_command method + for _name, cls in inspect.getmembers(sys.modules[__name__]): + if inspect.isclass(cls) and callable( + getattr(cls, "register_lldb_command", None) + ): + cls.register_lldb_command(debugger, __name__) diff --git a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp index 7f3359f..5f0cc4c 100644 --- a/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp +++ b/lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp @@ -218,6 +218,14 @@ bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallCommandObject( return false; } +bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallParsedCommandObject( + PyObject *implementor, lldb::DebuggerSP debugger, + StructuredDataImpl &args_impl, + lldb_private::CommandReturnObject &cmd_retobj, + lldb::ExecutionContextRefSP exe_ctx_ref_sp) { + return false; +} + bool lldb_private::python::SWIGBridge::LLDBSwigPythonCallModuleInit( const char *python_module_name, const char *session_dictionary_name, lldb::DebuggerSP debugger) { |