Skip to content

Commit 7e1432f

Browse files
authored
[lldb] Standardize command option parsing error messages (llvm#82273)
I have been looking to simplify parsing logic and improve the interfaces so that they are both easier to use and harder to abuse. To be specific, I am referring to functions such as `OptionArgParser::ToBoolean`: I would like to go from its current interface to something more like `llvm::Error<bool> ToBoolean(llvm::StringRef option_arg)`. Through working on that, I encountered 2 inconveniences: 1. Option parsing code is not uniform. Every function writes a slightly different error message, so incorporating an error message from the `ToBoolean` implementation is going to be laborious as I figure out what exactly needs to change or stay the same. 2. Changing the interface of `ToBoolean` would require a global atomic change across all of the Command code. This would be quite frustrating to do because of the non-uniformity of our existing code. To address these frustrations, I think it would be easiest to first standardize the error reporting mechanism when parsing options in commands. I do so by introducing `CreateOptionParsingError` which will create an error message of the shape: Invalid value ('${option_arg}') for -${short_value} ('${long_value}'): ${additional_context} Concretely, it would look something like this: (lldb) breakpoint set -n main -G yay error: Invalid value ('yay') for -G (auto-continue): Failed to parse as boolean After this, updating the interfaces for parsing the values themselves should become simpler. Because this can be adopted incrementally, this should be able to done over the course of time instead of all at once as a giant difficult-to-review change. I've changed exactly one function where this function would be used as an illustration of what I am proposing.
1 parent 11d115d commit 7e1432f

File tree

5 files changed

+96
-17
lines changed

5 files changed

+96
-17
lines changed

lldb/include/lldb/Interpreter/Options.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -336,6 +336,39 @@ class OptionGroupOptions : public Options {
336336
bool m_did_finalize = false;
337337
};
338338

339+
/// Creates an error that represents the failure to parse an command line option
340+
/// argument. This creates an error containing all information needed to show
341+
/// the developer what went wrong when parsing their command. It is recommended
342+
/// to use this instead of writing an error by hand.
343+
///
344+
/// \param[in] option_arg
345+
/// The argument that was attempted to be parsed.
346+
///
347+
/// \param[in] short_option
348+
/// The short form of the option. For example, if the flag is -f, the short
349+
/// option is "f".
350+
///
351+
/// \param[in] long_option
352+
/// The long form of the option. This field is optional. If the flag is
353+
/// --force, then the long option is "force".
354+
///
355+
/// \param[in] additional_context
356+
/// This is extra context that will get included in the error. This field is
357+
/// optional.
358+
///
359+
/// \return
360+
/// An llvm::Error that contains a standardized format for what went wrong
361+
/// when parsing and why.
362+
llvm::Error CreateOptionParsingError(llvm::StringRef option_arg,
363+
const char short_option,
364+
llvm::StringRef long_option = {},
365+
llvm::StringRef additional_context = {});
366+
367+
static constexpr llvm::StringLiteral g_bool_parsing_error_message =
368+
"Failed to parse as boolean";
369+
static constexpr llvm::StringLiteral g_int_parsing_error_message =
370+
"Failed to parse as integer";
371+
339372
} // namespace lldb_private
340373

341374
#endif // LLDB_INTERPRETER_OPTIONS_H

lldb/source/Commands/CommandObjectBreakpoint.cpp

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ class lldb_private::BreakpointOptionGroup : public OptionGroup {
6464
Status error;
6565
const int short_option =
6666
g_breakpoint_modify_options[option_idx].short_option;
67+
const char *long_option =
68+
g_breakpoint_modify_options[option_idx].long_option;
6769

6870
switch (short_option) {
6971
case 'c':
@@ -84,18 +86,17 @@ class lldb_private::BreakpointOptionGroup : public OptionGroup {
8486
case 'G': {
8587
bool value, success;
8688
value = OptionArgParser::ToBoolean(option_arg, false, &success);
87-
if (success) {
89+
if (success)
8890
m_bp_opts.SetAutoContinue(value);
89-
} else
90-
error.SetErrorStringWithFormat(
91-
"invalid boolean value '%s' passed for -G option",
92-
option_arg.str().c_str());
91+
else
92+
error = CreateOptionParsingError(option_arg, short_option, long_option,
93+
g_bool_parsing_error_message);
9394
} break;
9495
case 'i': {
9596
uint32_t ignore_count;
9697
if (option_arg.getAsInteger(0, ignore_count))
97-
error.SetErrorStringWithFormat("invalid ignore count '%s'",
98-
option_arg.str().c_str());
98+
error = CreateOptionParsingError(option_arg, short_option, long_option,
99+
g_int_parsing_error_message);
99100
else
100101
m_bp_opts.SetIgnoreCount(ignore_count);
101102
} break;
@@ -105,27 +106,29 @@ class lldb_private::BreakpointOptionGroup : public OptionGroup {
105106
if (success) {
106107
m_bp_opts.SetOneShot(value);
107108
} else
108-
error.SetErrorStringWithFormat(
109-
"invalid boolean value '%s' passed for -o option",
110-
option_arg.str().c_str());
109+
error = CreateOptionParsingError(option_arg, short_option, long_option,
110+
g_bool_parsing_error_message);
111111
} break;
112112
case 't': {
113113
lldb::tid_t thread_id = LLDB_INVALID_THREAD_ID;
114114
if (option_arg == "current") {
115115
if (!execution_context) {
116-
error.SetErrorStringWithFormat("No context to determine current "
117-
"thread");
116+
error = CreateOptionParsingError(
117+
option_arg, short_option, long_option,
118+
"No context to determine current thread");
118119
} else {
119120
ThreadSP ctx_thread_sp = execution_context->GetThreadSP();
120121
if (!ctx_thread_sp || !ctx_thread_sp->IsValid()) {
121-
error.SetErrorStringWithFormat("No currently selected thread");
122+
error =
123+
CreateOptionParsingError(option_arg, short_option, long_option,
124+
"No currently selected thread");
122125
} else {
123126
thread_id = ctx_thread_sp->GetID();
124127
}
125128
}
126129
} else if (option_arg.getAsInteger(0, thread_id)) {
127-
error.SetErrorStringWithFormat("invalid thread id string '%s'",
128-
option_arg.str().c_str());
130+
error = CreateOptionParsingError(option_arg, short_option, long_option,
131+
g_int_parsing_error_message);
129132
}
130133
if (thread_id != LLDB_INVALID_THREAD_ID)
131134
m_bp_opts.SetThreadID(thread_id);
@@ -139,8 +142,8 @@ class lldb_private::BreakpointOptionGroup : public OptionGroup {
139142
case 'x': {
140143
uint32_t thread_index = UINT32_MAX;
141144
if (option_arg.getAsInteger(0, thread_index)) {
142-
error.SetErrorStringWithFormat("invalid thread index string '%s'",
143-
option_arg.str().c_str());
145+
error = CreateOptionParsingError(option_arg, short_option, long_option,
146+
g_int_parsing_error_message);
144147
} else {
145148
m_bp_opts.GetThreadSpec()->SetIndex(thread_index);
146149
}

lldb/source/Interpreter/Options.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1365,3 +1365,16 @@ llvm::Expected<Args> Options::Parse(const Args &args,
13651365
argv.erase(argv.begin(), argv.begin() + OptionParser::GetOptionIndex());
13661366
return ReconstituteArgsAfterParsing(argv, args);
13671367
}
1368+
1369+
llvm::Error lldb_private::CreateOptionParsingError(
1370+
llvm::StringRef option_arg, const char short_option,
1371+
llvm::StringRef long_option, llvm::StringRef additional_context) {
1372+
std::string buffer;
1373+
llvm::raw_string_ostream stream(buffer);
1374+
stream << "Invalid value ('" << option_arg << "') for -" << short_option;
1375+
if (!long_option.empty())
1376+
stream << " (" << long_option << ")";
1377+
if (!additional_context.empty())
1378+
stream << ": " << additional_context;
1379+
return llvm::createStringError(llvm::inconvertibleErrorCode(), buffer);
1380+
}

lldb/unittests/Interpreter/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ add_lldb_unittest(InterpreterTests
22
TestCommandPaths.cpp
33
TestCompletion.cpp
44
TestOptionArgParser.cpp
5+
TestOptions.cpp
56
TestOptionValue.cpp
67
TestOptionValueFileColonLine.cpp
78
TestRegexCommand.cpp
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//===-- TestOptions.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 "lldb/Interpreter/Options.h"
10+
#include "gtest/gtest.h"
11+
12+
#include "llvm/Testing/Support/Error.h"
13+
14+
using namespace lldb_private;
15+
16+
TEST(OptionsTest, CreateOptionParsingError) {
17+
ASSERT_THAT_ERROR(
18+
CreateOptionParsingError("yippee", 'f', "fun",
19+
"unable to convert 'yippee' to boolean"),
20+
llvm::FailedWithMessage("Invalid value ('yippee') for -f (fun): unable "
21+
"to convert 'yippee' to boolean"));
22+
23+
ASSERT_THAT_ERROR(
24+
CreateOptionParsingError("52", 'b', "bean-count"),
25+
llvm::FailedWithMessage("Invalid value ('52') for -b (bean-count)"));
26+
27+
ASSERT_THAT_ERROR(CreateOptionParsingError("c", 'm'),
28+
llvm::FailedWithMessage("Invalid value ('c') for -m"));
29+
}

0 commit comments

Comments
 (0)