aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lldb/bindings/python/CMakeLists.txt4
-rw-r--r--lldb/bindings/python/python-wrapper.swig31
-rw-r--r--lldb/examples/python/cmdtemplate.py129
-rw-r--r--lldb/examples/python/templates/parsed_cmd.py360
-rw-r--r--lldb/include/lldb/Interpreter/CommandObject.h5
-rw-r--r--lldb/include/lldb/Interpreter/ScriptInterpreter.h29
-rw-r--r--lldb/source/Commands/CommandObjectCommands.cpp729
-rw-r--r--lldb/source/Commands/Options.td22
-rw-r--r--lldb/source/Interpreter/CommandObject.cpp17
-rw-r--r--lldb/source/Plugins/ScriptInterpreter/Python/PythonDataObjects.h2
-rw-r--r--lldb/source/Plugins/ScriptInterpreter/Python/SWIGPythonBridge.h7
-rw-r--r--lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp253
-rw-r--r--lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h21
-rw-r--r--lldb/test/API/commands/command/script/add/TestAddParsedCommand.py146
-rw-r--r--lldb/test/API/commands/command/script/add/test_commands.py174
-rw-r--r--lldb/unittests/ScriptInterpreter/Python/PythonTestSuite.cpp8
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) {