Skip to content

Commit 2a6dbed

Browse files
committed
[lldb] Fix (unintentional) recursion in CommandObjectRegexCommand
Jim noticed that the regex command is unintentionally recursive. Let's use the following command regex as an example: (lldb) com regex humm 's/([^ ]+) ([^ ]+)/p %1 %2 %1 %2/' If we call it with arguments foo bar, thing behave as expected: (lldb) humm foo bar (...) foo bar foo bar However, if we include %2 in the arguments, things break down: (lldb) humm fo%2o bar (...) fobaro bar fobaro bar The problem is that the implementation of the substitution is too naive. It substitutes the %1 token into the target template in place, then does the %2 substitution starting with the resultant string. So if the previous substitution introduced a %2 token, it would get processed in the second sweep, etc. This patch addresses the issue by walking the command once and substituting the % variables in place. (lldb) humm fo%2o bar (...) fo%2o bar fo%2o bar Furthermore, this patch also reports an error if not enough variables were provided and add support for substituting %0. rdar://81236994 Differential revision: https://reviews.llvm.org/D120101
1 parent 222e861 commit 2a6dbed

File tree

6 files changed

+125
-21
lines changed

6 files changed

+125
-21
lines changed

lldb/include/lldb/Interpreter/CommandReturnObject.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "lldb/lldb-private.h"
1616

1717
#include "llvm/ADT/StringRef.h"
18+
#include "llvm/Support/Error.h"
1819
#include "llvm/Support/FormatVariadic.h"
1920
#include "llvm/Support/WithColor.h"
2021

@@ -132,6 +133,8 @@ class CommandReturnObject {
132133

133134
void SetError(const Status &error, const char *fallback_error_cstr = nullptr);
134135

136+
void SetError(llvm::Error error);
137+
135138
lldb::ReturnStatus GetStatus() const;
136139

137140
void SetStatus(lldb::ReturnStatus status);

lldb/source/Commands/CommandObjectRegexCommand.cpp

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
#include "lldb/Interpreter/CommandInterpreter.h"
1111
#include "lldb/Interpreter/CommandReturnObject.h"
1212

13+
#include "llvm/Support/Errc.h"
14+
#include "llvm/Support/Error.h"
15+
1316
using namespace lldb;
1417
using namespace lldb_private;
1518

@@ -25,46 +28,64 @@ CommandObjectRegexCommand::CommandObjectRegexCommand(
2528
// Destructor
2629
CommandObjectRegexCommand::~CommandObjectRegexCommand() = default;
2730

31+
llvm::Expected<std::string> CommandObjectRegexCommand::SubstituteVariables(
32+
llvm::StringRef input,
33+
const llvm::SmallVectorImpl<llvm::StringRef> &replacements) {
34+
std::string buffer;
35+
llvm::raw_string_ostream output(buffer);
36+
37+
llvm::SmallVector<llvm::StringRef, 4> parts;
38+
input.split(parts, '%');
39+
40+
output << parts[0];
41+
for (llvm::StringRef part : drop_begin(parts)) {
42+
size_t idx = 0;
43+
if (part.consumeInteger(10, idx))
44+
output << '%';
45+
else if (idx < replacements.size())
46+
output << replacements[idx];
47+
else
48+
return llvm::make_error<llvm::StringError>(
49+
llvm::formatv("%{0} is out of range: not enough arguments specified",
50+
idx),
51+
llvm::errc::invalid_argument);
52+
output << part;
53+
}
54+
55+
return output.str();
56+
}
57+
2858
bool CommandObjectRegexCommand::DoExecute(llvm::StringRef command,
2959
CommandReturnObject &result) {
3060
EntryCollection::const_iterator pos, end = m_entries.end();
3161
for (pos = m_entries.begin(); pos != end; ++pos) {
3262
llvm::SmallVector<llvm::StringRef, 4> matches;
3363
if (pos->regex.Execute(command, &matches)) {
34-
std::string new_command(pos->command);
35-
char percent_var[8];
36-
size_t idx, percent_var_idx;
37-
for (uint32_t match_idx = 1; match_idx <= m_max_matches; ++match_idx) {
38-
if (match_idx < matches.size()) {
39-
const std::string match_str = matches[match_idx].str();
40-
const int percent_var_len =
41-
::snprintf(percent_var, sizeof(percent_var), "%%%u", match_idx);
42-
for (idx = 0; (percent_var_idx = new_command.find(
43-
percent_var, idx)) != std::string::npos;) {
44-
new_command.erase(percent_var_idx, percent_var_len);
45-
new_command.insert(percent_var_idx, match_str);
46-
idx = percent_var_idx + match_str.size();
47-
}
48-
}
64+
llvm::Expected<std::string> new_command =
65+
SubstituteVariables(pos->command, matches);
66+
if (!new_command) {
67+
result.SetError(new_command.takeError());
68+
return false;
4969
}
70+
5071
// Interpret the new command and return this as the result!
5172
if (m_interpreter.GetExpandRegexAliases())
52-
result.GetOutputStream().Printf("%s\n", new_command.c_str());
73+
result.GetOutputStream().Printf("%s\n", new_command->c_str());
5374
// Pass in true for "no context switching". The command that called us
5475
// should have set up the context appropriately, we shouldn't have to
5576
// redo that.
56-
return m_interpreter.HandleCommand(new_command.c_str(),
77+
return m_interpreter.HandleCommand(new_command->c_str(),
5778
eLazyBoolCalculate, result);
5879
}
5980
}
6081
result.SetStatus(eReturnStatusFailed);
6182
if (!GetSyntax().empty())
6283
result.AppendError(GetSyntax());
6384
else
64-
result.GetOutputStream() << "Command contents '" << command
65-
<< "' failed to match any "
66-
"regular expression in the '"
67-
<< m_cmd_name << "' regex ";
85+
result.GetErrorStream() << "Command contents '" << command
86+
<< "' failed to match any "
87+
"regular expression in the '"
88+
<< m_cmd_name << "' regex ";
6889
return false;
6990
}
7091

lldb/source/Commands/CommandObjectRegexCommand.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ class CommandObjectRegexCommand : public CommandObjectRaw {
3939
protected:
4040
bool DoExecute(llvm::StringRef command, CommandReturnObject &result) override;
4141

42+
/// Substitute variables of the format %\d+ in the input string.
43+
static llvm::Expected<std::string> SubstituteVariables(
44+
llvm::StringRef input,
45+
const llvm::SmallVectorImpl<llvm::StringRef> &replacements);
46+
4247
struct Entry {
4348
RegularExpression regex;
4449
std::string command;

lldb/source/Interpreter/CommandReturnObject.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ void CommandReturnObject::SetError(const Status &error,
109109
AppendError(error.AsCString(fallback_error_cstr));
110110
}
111111

112+
void CommandReturnObject::SetError(llvm::Error error) {
113+
if (error)
114+
AppendError(llvm::toString(std::move(error)));
115+
}
116+
112117
// Similar to AppendError, but do not prepend 'Status: ' to message, and don't
113118
// append "\n" to the end of it.
114119

lldb/unittests/Interpreter/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ add_lldb_unittest(InterpreterTests
44
TestOptionArgParser.cpp
55
TestOptionValue.cpp
66
TestOptionValueFileColonLine.cpp
7+
TestRegexCommand.cpp
78

89
LINK_LIBS
910
lldbCore
@@ -14,4 +15,5 @@ add_lldb_unittest(InterpreterTests
1415
lldbUtilityHelpers
1516
lldbInterpreter
1617
lldbPluginPlatformMacOSX
18+
LLVMTestingSupport
1719
)
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
//===-- TestRegexCommand.cpp ----------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "Commands/CommandObjectRegexCommand.h"
10+
#include "llvm/Testing/Support/Error.h"
11+
#include "gtest/gtest.h"
12+
13+
using namespace lldb_private;
14+
using namespace lldb;
15+
16+
namespace {
17+
class TestRegexCommand : public CommandObjectRegexCommand {
18+
public:
19+
using CommandObjectRegexCommand::SubstituteVariables;
20+
21+
static std::string
22+
Substitute(llvm::StringRef input,
23+
const llvm::SmallVectorImpl<llvm::StringRef> &replacements) {
24+
llvm::Expected<std::string> str = SubstituteVariables(input, replacements);
25+
if (!str)
26+
return llvm::toString(str.takeError());
27+
return *str;
28+
}
29+
};
30+
} // namespace
31+
32+
TEST(RegexCommandTest, SubstituteVariablesSuccess) {
33+
const llvm::SmallVector<llvm::StringRef, 4> substitutions = {"all", "foo",
34+
"bar", "baz"};
35+
36+
EXPECT_EQ(TestRegexCommand::Substitute("%0", substitutions), "all");
37+
EXPECT_EQ(TestRegexCommand::Substitute("%1", substitutions), "foo");
38+
EXPECT_EQ(TestRegexCommand::Substitute("%2", substitutions), "bar");
39+
EXPECT_EQ(TestRegexCommand::Substitute("%3", substitutions), "baz");
40+
EXPECT_EQ(TestRegexCommand::Substitute("%1%2%3", substitutions), "foobarbaz");
41+
EXPECT_EQ(TestRegexCommand::Substitute("#%1#%2#%3#", substitutions),
42+
"#foo#bar#baz#");
43+
}
44+
45+
TEST(RegexCommandTest, SubstituteVariablesFailed) {
46+
const llvm::SmallVector<llvm::StringRef, 4> substitutions = {"all", "foo",
47+
"bar", "baz"};
48+
49+
ASSERT_THAT_EXPECTED(
50+
TestRegexCommand::SubstituteVariables("%1%2%3%4", substitutions),
51+
llvm::Failed());
52+
ASSERT_THAT_EXPECTED(
53+
TestRegexCommand::SubstituteVariables("%5", substitutions),
54+
llvm::Failed());
55+
ASSERT_THAT_EXPECTED(
56+
TestRegexCommand::SubstituteVariables("%11", substitutions),
57+
llvm::Failed());
58+
}
59+
60+
TEST(RegexCommandTest, SubstituteVariablesNoRecursion) {
61+
const llvm::SmallVector<llvm::StringRef, 4> substitutions = {"all", "%2",
62+
"%3", "%4"};
63+
EXPECT_EQ(TestRegexCommand::Substitute("%0", substitutions), "all");
64+
EXPECT_EQ(TestRegexCommand::Substitute("%1", substitutions), "%2");
65+
EXPECT_EQ(TestRegexCommand::Substitute("%2", substitutions), "%3");
66+
EXPECT_EQ(TestRegexCommand::Substitute("%3", substitutions), "%4");
67+
EXPECT_EQ(TestRegexCommand::Substitute("%1%2%3", substitutions), "%2%3%4");
68+
}

0 commit comments

Comments
 (0)