jingham created this revision. jingham added reviewers: JDevlieghere, clayborg. Herald added a subscriber: jeroen.dobbelaere. Herald added a project: All. jingham requested review of this revision. Herald added a project: LLDB. Herald added a subscriber: lldb-commits.
At present, backtick substitution is a "preprocessor" operation, which is not, I think, what people expect. I'm pretty sure you didn't mean to run an expression when you specified a file name that happened to have backticks in it, etc. I think it makes a lot more sense that the substitution should be done when it is encountered as a quote character. There was, in fact, some code in CommandObjectParsed that attempted to treat it this way, but it was defeated by the fact that we unconditionally do PreprocessCommand on the command string before parsing. It was also not quite right since it assumed the quote would still be present in the argument value, when in fact it was just in the quote member of the ArgEntry... I fixed that, and changed the CommandInterpreter code to only preprocess the whole string for raw commands. I think really that Raw commands should handle their own preprocessing, for instance I don't think doing backtick substitution in the expression passed to `expr` is really what people expect... When I did that a bunch of tests failed because we were not correctly preserving the backticks around arguments when we parsed them into a temporary argv array and then back out again, and because we didn't take them into account when stripping elements out of the incoming command when handling command aliases. I also added a test that explicitly tests command aliases with embedded backticks. The test has a succeeding case and a failing case. The latter shows an extant bug this patch doesn't fix: the CommandAlias::Desugar doesn't correctly handle the case where you alias an alias and the second alias fills in slots of the first alias. It just pastes the second aliases options after the first aliases and then tries to evaluate them, which ends up with unfilled slots. As I say, that's an extant bug so the test for it fails now. I'll fix that one next chance I have to mess with this. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D133045 Files: lldb/include/lldb/Interpreter/CommandInterpreter.h lldb/packages/Python/lldbsuite/test/lldbtest.py lldb/source/Interpreter/CommandAlias.cpp lldb/source/Interpreter/CommandInterpreter.cpp lldb/source/Interpreter/CommandObject.cpp lldb/source/Interpreter/Options.cpp lldb/source/Utility/Args.cpp lldb/test/API/commands/command/backticks/Makefile lldb/test/API/commands/command/backticks/TestBackticksInAlias.py lldb/test/API/commands/command/backticks/main.c
Index: lldb/test/API/commands/command/backticks/main.c =================================================================== --- /dev/null +++ lldb/test/API/commands/command/backticks/main.c @@ -0,0 +1,5 @@ +int main (int argc, char const *argv[]) +{ + return argc; // break here +} + Index: lldb/test/API/commands/command/backticks/TestBackticksInAlias.py =================================================================== --- /dev/null +++ lldb/test/API/commands/command/backticks/TestBackticksInAlias.py @@ -0,0 +1,32 @@ +""" +Test that an alias can contain active backticks +""" + + + +import lldb +from lldbsuite.test.lldbtest import * +import lldbsuite.test.lldbutil as lldbutil + + +class TestBackticksInAlias(TestBase): + NO_DEBUG_INFO_TESTCASE = True + + def test_backticks_in_alias(self): + """Test that an alias can contain active backtraces.""" + self.build() + (target, process, thread, bkpt) = lldbutil.run_to_source_breakpoint(self, "break here", lldb.SBFileSpec("main.c")) + interp = self.dbg.GetCommandInterpreter(); + result = lldb.SBCommandReturnObject() + interp.HandleCommand('command alias _test-argv-cmd expression -Z \`argc\` -- argv', result) + self.assertCommandReturn(result, "Made the alias") + interp.HandleCommand("_test-argv-cmd", result) + self.assertCommandReturn(result, "The alias worked") + + # Now try a harder case where we create this using an alias: + interp.HandleCommand('command alias _test-argv-parray-cmd parray \`argc\` argv', result) + self.assertCommandReturn(result, "Made the alias") + interp.HandleCommand("_test-argv-parray-cmd", result) + self.assertFalse(result.Succeeded(), "CommandAlias::Desugar currently fails if a alias substitutes %N arguments in another alias") + + Index: lldb/test/API/commands/command/backticks/Makefile =================================================================== --- /dev/null +++ lldb/test/API/commands/command/backticks/Makefile @@ -0,0 +1,3 @@ +C_SOURCES := main.c + +include Makefile.rules Index: lldb/source/Utility/Args.cpp =================================================================== --- lldb/source/Utility/Args.cpp +++ lldb/source/Utility/Args.cpp @@ -215,7 +215,12 @@ for (size_t i = 0; i < m_entries.size(); ++i) { if (i > 0) command += ' '; + char quote = m_entries[i].quote; + if (quote != '\0') + command += quote; command += m_entries[i].ref(); + if (quote != '\0') + command += quote; } return !m_entries.empty(); Index: lldb/source/Interpreter/Options.cpp =================================================================== --- lldb/source/Interpreter/Options.cpp +++ lldb/source/Interpreter/Options.cpp @@ -1002,37 +1002,47 @@ .str(), llvm::inconvertibleErrorCode()); } - if (!option_arg) - option_arg = "<no-argument>"; - option_arg_vector->emplace_back(std::string(option_str.GetString()), - has_arg, std::string(option_arg)); - // Find option in the argument list; also see if it was supposed to take an // argument and if one was supplied. Remove option (and argument, if // given) from the argument list. Also remove them from the // raw_input_string, if one was passed in. + // Note: We also need to preserve any option argument values that were + // surrounded by backticks, as we lose track of them in the + // option_args_vector. size_t idx = FindArgumentIndexForOption(args_copy, long_options[long_options_index]); + std::string option_to_insert; + if (option_arg) { + if (idx != size_t(-1) && has_arg) { + bool arg_has_backtick = args_copy[idx + 1].GetQuoteChar() == '`'; + if (arg_has_backtick) + option_to_insert = "`"; + option_to_insert += option_arg; + if (arg_has_backtick) + option_to_insert += "`"; + } else + option_to_insert = option_arg; + } else + option_to_insert = CommandInterpreter::g_no_argument; + + option_arg_vector->emplace_back(std::string(option_str.GetString()), + has_arg, option_to_insert); + if (idx == size_t(-1)) continue; if (!input_line.empty()) { - auto tmp_arg = args_copy[idx].ref(); + llvm::StringRef tmp_arg = args_copy[idx].ref(); size_t pos = input_line.find(std::string(tmp_arg)); if (pos != std::string::npos) input_line.erase(pos, tmp_arg.size()); } args_copy.DeleteArgumentAtIndex(idx); - if ((long_options[long_options_index].definition->option_has_arg != - OptionParser::eNoArgument) && - (OptionParser::GetOptionArgument() != nullptr) && - (idx < args_copy.GetArgumentCount()) && - (args_copy[idx].ref() == OptionParser::GetOptionArgument())) { + if (option_to_insert != CommandInterpreter::g_no_argument) { if (input_line.size() > 0) { - auto tmp_arg = args_copy[idx].ref(); - size_t pos = input_line.find(std::string(tmp_arg)); + size_t pos = input_line.find(option_to_insert); if (pos != std::string::npos) - input_line.erase(pos, tmp_arg.size()); + input_line.erase(pos, option_to_insert.size()); } args_copy.DeleteArgumentAtIndex(idx); } Index: lldb/source/Interpreter/CommandObject.cpp =================================================================== --- lldb/source/Interpreter/CommandObject.cpp +++ lldb/source/Interpreter/CommandObject.cpp @@ -727,7 +727,7 @@ } if (!handled) { for (auto entry : llvm::enumerate(cmd_args.entries())) { - if (!entry.value().ref().empty() && entry.value().ref().front() == '`') { + if (!entry.value().ref().empty() && entry.value().GetQuoteChar() == '`') { cmd_args.ReplaceArgumentAtIndex( entry.index(), m_interpreter.ProcessEmbeddedScriptCommands(entry.value().c_str())); Index: lldb/source/Interpreter/CommandInterpreter.cpp =================================================================== --- lldb/source/Interpreter/CommandInterpreter.cpp +++ lldb/source/Interpreter/CommandInterpreter.cpp @@ -105,6 +105,11 @@ "and\n" "accept the security risk."; +constexpr const char *CommandInterpreter::g_no_argument = "<no-argument>"; +constexpr const char *CommandInterpreter::g_need_argument = "<need-argument>"; +constexpr const char *CommandInterpreter::g_argument = "<argument>"; + + #define LLDB_PROPERTIES_interpreter #include "InterpreterProperties.inc" @@ -1634,7 +1639,7 @@ std::string value; for (const auto &entry : *option_arg_vector) { std::tie(option, value_type, value) = entry; - if (option == "<argument>") { + if (option == g_argument) { result_str.Printf(" %s", value.c_str()); continue; } @@ -1656,11 +1661,33 @@ index); return nullptr; } else { - size_t strpos = raw_input_string.find(cmd_args.GetArgumentAtIndex(index)); - if (strpos != std::string::npos) + const Args::ArgEntry &entry = cmd_args[index]; + size_t strpos = raw_input_string.find(entry.c_str()); + const char quote_char = entry.GetQuoteChar(); + if (strpos != std::string::npos) { + const size_t start_fudge = quote_char == '\0' ? 0 : 1; + const size_t len_fudge = quote_char == '\0' ? 0 : 2; + + // Make sure we aren't going outside the bounds of the cmd string: + if (strpos < start_fudge) { + result.AppendError("Unmatched quote at command beginning."); + return nullptr; + } + llvm::StringRef arg_text = entry.ref(); + if (strpos - start_fudge + arg_text.size() + len_fudge + > raw_input_string.size()) { + result.AppendError("Unmatched quote at command end."); + return nullptr; + } raw_input_string = raw_input_string.erase( - strpos, strlen(cmd_args.GetArgumentAtIndex(index))); - result_str.Printf("%s", cmd_args.GetArgumentAtIndex(index)); + strpos - start_fudge, + strlen(cmd_args.GetArgumentAtIndex(index)) + len_fudge); + } + if (quote_char == '\0') + result_str.Printf("%s", cmd_args.GetArgumentAtIndex(index)); + else + result_str.Printf("%c%s%c", quote_char, + entry.c_str(), quote_char); } } @@ -1911,13 +1938,6 @@ return true; } - Status error(PreprocessCommand(command_string)); - - if (error.Fail()) { - result.AppendError(error.AsCString()); - return false; - } - // Phase 1. // Before we do ANY kind of argument processing, we need to figure out what @@ -1935,6 +1955,20 @@ CommandObject *cmd_obj = ResolveCommandImpl(command_string, result); + // We have to preprocess the whole command string for Raw commands, since we + // don't know the structure of the command. For parsed commands, we only + // treat backticks as quote characters specially. + // FIXME: We probably want to have raw commands do their own preprocessing. + // For instance, I don't think people expect substitution in expr expressions. + if (cmd_obj && cmd_obj->WantsRawCommandString()) { + Status error(PreprocessCommand(command_string)); + + if (error.Fail()) { + result.AppendError(error.AsCString()); + return false; + } + } + // Although the user may have abbreviated the command, the command_string now // has the command expanded to the full name. For example, if the input was // "br s -n main", command_string is now "breakpoint set -n main". @@ -2163,7 +2197,7 @@ std::string value; for (const auto &option_entry : *option_arg_vector) { std::tie(option, value_type, value) = option_entry; - if (option == "<argument>") { + if (option == g_argument) { if (!wants_raw_input || (value != "--")) { // Since we inserted this above, make sure we don't insert it twice new_args.AppendArgument(value); @@ -2174,7 +2208,7 @@ if (value_type != OptionParser::eOptionalArgument) new_args.AppendArgument(option); - if (value == "<no-argument>") + if (value == g_no_argument) continue; int index = GetOptionArgumentPosition(value.c_str()); Index: lldb/source/Interpreter/CommandAlias.cpp =================================================================== --- lldb/source/Interpreter/CommandAlias.cpp +++ lldb/source/Interpreter/CommandAlias.cpp @@ -60,11 +60,12 @@ if (!options_string.empty()) { if (cmd_obj_sp->WantsRawCommandString()) - option_arg_vector->emplace_back("<argument>", -1, options_string); + option_arg_vector->emplace_back(CommandInterpreter::g_argument, + -1, options_string); else { for (auto &entry : args.entries()) { if (!entry.ref().empty()) - option_arg_vector->emplace_back(std::string("<argument>"), -1, + option_arg_vector->emplace_back(std::string(CommandInterpreter::g_argument), -1, std::string(entry.ref())); } } @@ -153,11 +154,12 @@ for (const auto &opt_entry : *options) { std::tie(opt, std::ignore, value) = opt_entry; - if (opt == "<argument>") { + if (opt == CommandInterpreter::g_argument) { help_string.Printf(" %s", value.c_str()); } else { help_string.Printf(" %s", opt.c_str()); - if ((value != "<no-argument>") && (value != "<need-argument")) { + if ((value != CommandInterpreter::g_no_argument) + && (value != CommandInterpreter::g_need_argument)) { help_string.Printf(" %s", value.c_str()); } } @@ -178,7 +180,7 @@ for (const auto &opt_entry : *GetOptionArguments()) { std::tie(opt, std::ignore, value) = opt_entry; - if (opt == "<argument>" && !value.empty() && + if (opt == CommandInterpreter::g_argument && !value.empty() && llvm::StringRef(value).endswith("--")) { m_is_dashdash_alias = eLazyBoolYes; break; @@ -206,6 +208,8 @@ return {nullptr, nullptr}; if (underlying->IsAlias()) { + // FIXME: This doesn't work if the original alias fills a slot in the + // underlying alias, since this just appends the two lists. auto desugared = ((CommandAlias *)underlying.get())->Desugar(); auto options = GetOptionArguments(); options->insert(options->begin(), desugared.second->begin(), Index: lldb/packages/Python/lldbsuite/test/lldbtest.py =================================================================== --- lldb/packages/Python/lldbsuite/test/lldbtest.py +++ lldb/packages/Python/lldbsuite/test/lldbtest.py @@ -2471,6 +2471,13 @@ self.fail(self._formatMessage(msg, "'{}' is not success".format(error))) + """Assert that a command return object is successful""" + def assertCommandReturn(self, obj, msg=None): + if not obj.Succeeded(): + error = obj.GetError() + self.fail(self._formatMessage(msg, + "'{}' is not success".format(error))) + """Assert two states are equal""" def assertState(self, first, second, msg=None): if first != second: Index: lldb/include/lldb/Interpreter/CommandInterpreter.h =================================================================== --- lldb/include/lldb/Interpreter/CommandInterpreter.h +++ lldb/include/lldb/Interpreter/CommandInterpreter.h @@ -239,6 +239,14 @@ eCommandTypesAllThem = 0xFFFF //< all commands }; + // The CommandAlias and CommandInterpreter both have a hand in + // substituting for alias commands. They work by writing special tokens + // in the template form of the Alias command, and then detecting them when the + // command is executed. These are the special tokens: + static const char * const g_no_argument; + static const char * const g_need_argument; + static const char * const g_argument; + CommandInterpreter(Debugger &debugger, bool synchronous_execution); ~CommandInterpreter() override = default;
_______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits