1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-05-05 06:33:52 +00:00

add some reduction methods to the options on the fuzz tests (#930)

This adds a round trip test for config file generation to the fuzzer. 

(the next step after this PR will be a fuzzer that verifies that the
round trip actually matches the results.
This change ended up requiring quite a few minor changes to fix the
ambiguities between the config file generation and config file reader.

1). There was a number of potential conflicts between positional names
and regular option names that could be triggered in config files, this
required a number of additional checks on the positional naming to
ensure no conflicts.
2). flag options with disable flag override can produce output results
that are not valid by themselves, resolving this required flag input to
be able to handle an array and output the original value set of results.
3). strings with non-printable characters could cause all sorts of chaos
in the config files. This was resolved by generating a binary string
conversion format and handling multiline comments and characters, and
handling escaped characters. Note; I think a better solution is to move
to fully supporting string formatting and escaping along with the binary
strings from TOML now that TOML 1.0 is finalized. That will not be this
PR though, maybe the next one.
4). Lot of ambiguities and edge cases in the string splitter, this was
reworked
5). handling of comments was not done well, especially comment characters in the
name of the option which is allowed.
6). non printable characters in the option naming. This would be weird
in practice but it also cause some big holes in the config file
generation, so the restricted character set for option naming was
expanded. (don't allow spaces or control characters).

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
Philip Top 2023-12-18 05:21:32 -08:00 committed by GitHub
parent 3bc2739c11
commit 0f5bf21e91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
58 changed files with 846 additions and 114 deletions

18
.codacy.yml Normal file
View File

@ -0,0 +1,18 @@
---
engines:
rubocop:
enabled: true
duplication:
enabled: true
metrics:
enabled: true
coverage:
enabled: false
languages:
exclude_paths:
- "fuzz/**/*"
- "fuzz/*"
- "scripts/**/*"
- "scripts/*"
- "**.md"

View File

@ -9,6 +9,7 @@ filter=-readability/nolint # Conflicts with clang-tidy
filter=-readability/check # Catch uses CHECK(a == b) (Tests only)
filter=-build/namespaces # Currently using it for one test (Tests only)
filter=-runtime/references # Requires fundamental change of API, don't see need for this
filter=-runtime/string # Requires not using static const strings which makes thing really annoying
filter=-whitespace/blank_line # Unnecessarily strict with blank lines that otherwise help with readability
filter=-whitespace/indent # Requires strange 3-space indent of private/protected/public markers
filter=-whitespace/parens,-whitespace/braces # Conflict with clang-format

View File

@ -206,6 +206,16 @@ str3 = """\
The key is that the closing of the multiline string must be at the end of a line
and match the starting 3 quote sequence.
### Binary Strings
Config files have a binary conversion capability, this is mainly to support
writing config files but can be used by user generated files as well. Strings
with the form `B"(XXXXX)"` will convert any characters inside the parenthesis
with the form \xHH to the equivalent binary value. The HH are hexadecimal
characters. Characters not in this form will be translated as given. If argument
values with unprintable characters are used to generate a config file this
binary form will be used in the output string.
## Multiple configuration files
If it is desired that multiple configuration be allowed. Use

View File

@ -40,10 +40,16 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
try {
app->parse(parseString);
} catch(const CLI::ParseError &e) {
//(app)->exit(e);
// this just indicates we caught an error known by CLI
}
return 0; // Non-zero return values are reserved for future use.
}
// should be able to write the config to a file and read from it again
std::string configOut = app->config_to_str();
app->clear();
std::stringstream out(configOut);
app->parse_from_stream(out);
return 0;
}

View File

@ -22,6 +22,13 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
auto app = fuzzdata.generateApp();
try {
app->parse_from_stream(out);
// should be able to write the config to a file and read from it again
std::string configOut = app->config_to_str();
app->clear();
std::stringstream out(configOut);
app->parse_from_stream(out);
} catch(const CLI::ParseError &e) {
// (app)->exit(e);
// this just indicates we caught an error known by CLI

View File

@ -68,9 +68,9 @@ std::shared_ptr<CLI::App> FuzzApp::generateApp() {
auto *vgroup = fApp->add_option_group("vectors");
vgroup->add_option("--vopt1", vv1);
vgroup->add_option("--vopt2", vvs);
vgroup->add_option("--vopt2", vvs)->inject_separator();
vgroup->add_option("--vopt3", vstr);
vgroup->add_option("--vopt4", vecvecd);
vgroup->add_option("--vopt4", vecvecd)->inject_separator();
fApp->add_option("--oopt1", od1);
fApp->add_option("--oopt2", ods);
@ -121,6 +121,30 @@ std::shared_ptr<CLI::App> FuzzApp::generateApp() {
sub->add_option("--sdwrap", dwrap);
sub->add_option("--siwrap", iwrap);
auto *resgroup = fApp->add_option_group("outputOrder");
resgroup->add_option("--vA", vstrA)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::TakeAll);
resgroup->add_option("--vB", vstrB)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
resgroup->add_option("--vC", vstrC)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst);
resgroup->add_option("--vD", vstrD)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::Reverse);
resgroup->add_option("--vS", val32)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::Sum);
resgroup->add_option("--vM", mergeBuffer)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::Join);
resgroup->add_option("--vE", vstrE)->expected(2, 4)->delimiter(',');
auto *vldtr = fApp->add_option_group("validators");
validator_strings.resize(10);
vldtr->add_option("--vdtr1", validator_strings[0])->join()->check(CLI::PositiveNumber);
vldtr->add_option("--vdtr2", validator_strings[1])->join()->check(CLI::NonNegativeNumber);
vldtr->add_option("--vdtr3", validator_strings[2])->join()->check(CLI::NonexistentPath);
vldtr->add_option("--vdtr4", validator_strings[3])->join()->check(CLI::Range(7, 3456));
vldtr->add_option("--vdtr5", validator_strings[4])
->join()
->check(CLI::Range(std::string("aa"), std::string("zz"), "string range"));
vldtr->add_option("--vdtr6", validator_strings[5])->join()->check(CLI::TypeValidator<double>());
vldtr->add_option("--vdtr7", validator_strings[6])->join()->check(CLI::TypeValidator<bool>());
vldtr->add_option("--vdtr8", validator_strings[7])->join()->check(CLI::ValidIPV4);
vldtr->add_option("--vdtr9", validator_strings[8])->join()->transform(CLI::Bound(2, 255));
return fApp;
}

View File

@ -75,6 +75,7 @@ class FuzzApp {
std::vector<double> vv1{};
std::vector<std::string> vstr{};
std::vector<std::vector<double>> vecvecd{};
std::vector<std::vector<std::string>> vvs{};
std::optional<double> od1{};
@ -103,5 +104,15 @@ class FuzzApp {
std::string buffer{};
int intbuffer{0};
std::atomic<double> doubleAtomic{0.0};
// for testing restrictions and reduction methods
std::vector<std::string> vstrA{};
std::vector<std::string> vstrB{};
std::vector<std::string> vstrC{};
std::vector<std::string> vstrD{};
std::vector<std::string> vstrE{};
std::vector<std::string> vstrF{};
std::string mergeBuffer{};
std::vector<std::string> validator_strings{};
};
} // namespace CLI

View File

@ -20,5 +20,6 @@ int main(int argc, char **argv) {
(app)->exit(e);
// this just indicates we caught an error known by CLI
}
return 0;
}

View File

@ -66,6 +66,14 @@
"--siwrap"
"--svtup"
"--satd"
"--vA"
"--vB"
"--vC"
"--vD"
"--vS"
"--vM"
"--vE"
"--vdtr"
"nflag2"
"stup1"
"svtup"
@ -143,6 +151,7 @@
"stup4"
"sdwrap"
"siwrap"
"vdtr"
"svtup"
"satd"
"%%"
@ -156,3 +165,10 @@
"!"
"{"
"}"
"vA"
"vB"
"vC"
"vD"
"vS"
"vM"
"vE"

View File

@ -72,6 +72,7 @@
"svopt4"
"config"
"nflag2"
"vdtr"
"--"
"fuzzer"
"t-"
@ -82,3 +83,10 @@
"dexists"
"fexists"
"fnexists"
"vA"
"vB"
"vC"
"vD"
"vS"
"vM"
"vE"

View File

@ -24,7 +24,10 @@ namespace CLI {
// [CLI11:config_hpp:verbatim]
namespace detail {
std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\'');
std::string convert_arg_for_ini(const std::string &arg,
char stringQuote = '"',
char characterQuote = '\'',
bool disable_multi_line = false);
/// Comma separated join, adds quotes if needed
std::string ini_join(const std::vector<std::string> &args,

View File

@ -10,6 +10,7 @@
#include <algorithm>
#include <fstream>
#include <iostream>
#include <memory>
#include <string>
#include <vector>
// [CLI11:public_includes:end]
@ -29,7 +30,6 @@ struct ConfigItem {
/// This is the name
std::string name{};
/// Listing of inputs
std::vector<std::string> inputs{};

View File

@ -127,6 +127,9 @@ class BadNameString : public ConstructionError {
return BadNameString("Long names strings require 2 dashes " + name);
}
static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); }
static BadNameString BadPositionalName(std::string name) {
return BadNameString("Invalid positional Name: " + name);
}
static BadNameString DashesOnly(std::string name) {
return BadNameString("Must have a name, not just dashes: " + name);
}

View File

@ -550,12 +550,12 @@ class Option : public OptionBase<Option> {
if(!lnames_.empty()) {
return lnames_[0];
}
if(!pname_.empty()) {
return pname_;
}
if(!snames_.empty()) {
return snames_[0];
}
if(!pname_.empty()) {
return pname_;
}
return envname_;
}
/// The number of times the option expects to be included
@ -578,13 +578,13 @@ class Option : public OptionBase<Option> {
CLI11_NODISCARD int get_items_expected() const { return get_items_expected_min(); }
/// True if the argument can be given directly
CLI11_NODISCARD bool get_positional() const { return pname_.length() > 0; }
CLI11_NODISCARD bool get_positional() const { return !pname_.empty(); }
/// True if option has at least one non-positional name
CLI11_NODISCARD bool nonpositional() const { return (snames_.size() + lnames_.size()) > 0; }
CLI11_NODISCARD bool nonpositional() const { return (!lnames_.empty() || !snames_.empty()); }
/// True if option has description
CLI11_NODISCARD bool has_description() const { return description_.length() > 0; }
CLI11_NODISCARD bool has_description() const { return !description_.empty(); }
/// Get the description
CLI11_NODISCARD const std::string &get_description() const { return description_; }

View File

@ -140,14 +140,16 @@ CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector<s
/// Verify the first character of an option
/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with
template <typename T> bool valid_first_char(T c) { return ((c != '-') && (c != '!') && (c != ' ') && c != '\n'); }
template <typename T> bool valid_first_char(T c) {
return ((c != '-') && (static_cast<unsigned char>(c) > 33)); // space and '!' not allowed
}
/// Verify following characters of an option
template <typename T> bool valid_later_char(T c) {
// = and : are value separators, { has special meaning for option defaults,
// and \n would just be annoying to deal with in many places allowing space here has too much potential for
// inadvertent entry errors and bugs
return ((c != '=') && (c != ':') && (c != '{') && (c != ' ') && c != '\n');
// and control codes other than tab would just be annoying to deal with in many places allowing space here has too
// much potential for inadvertent entry errors and bugs
return ((c != '=') && (c != ':') && (c != '{') && ((static_cast<unsigned char>(c) > 32) || c == '\t'));
}
/// Verify an option/subcommand name
@ -211,8 +213,11 @@ template <typename Callable> inline std::string find_and_modify(std::string str,
}
/// Split a string '"one two" "three"' into 'one two', 'three'
/// Quote characters can be ` ' or "
CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter = '\0');
/// Quote characters can be ` ' or " or bracket characters [{(< with matching to the matching bracket
CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter = '\0', bool removeQuotes = true);
/// get the value of an environmental variable or empty string if empty
CLI11_INLINE std::string get_environment_value(const std::string &env_name);
/// This function detects an equal or colon followed by an escaped quote after an argument
/// then modifies the string to replace the equality with a space. This is needed
@ -220,8 +225,27 @@ CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter =
/// the return value is the offset+1 which is required by the find_and_modify function.
CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset);
/// get the value of an environmental variable or empty string if empty
CLI11_INLINE std::string get_environment_value(const std::string &env_name);
/// @brief detect if a string has escapable characters
/// @param str the string to do the detection on
/// @return true if the string has escapable characters
CLI11_INLINE bool has_escapable_character(const std::string &str);
/// @brief escape all escapable characters
/// @param str the string to escape
/// @return a string with the escapble characters escaped with '\'
CLI11_INLINE std::string add_escaped_characters(const std::string &str);
/// @brief replace the escaped characters with their equivalent
CLI11_INLINE std::string remove_escaped_characters(const std::string &str);
/// generate a string with all non printable characters escaped to hex codes
CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape);
CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string);
/// extract an escaped binary_string
CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string);
} // namespace detail
// [CLI11:string_tools_hpp:end]

View File

@ -163,6 +163,32 @@ CLI11_INLINE Option *App::add_option(std::string option_name,
if(std::find_if(std::begin(options_), std::end(options_), [&myopt](const Option_p &v) { return *v == myopt; }) ==
std::end(options_)) {
if(myopt.lnames_.empty() && myopt.snames_.empty()) {
// if the option is positional only there is additional potential for ambiguities in config files and needs
// to be checked
std::string test_name = "--" + myopt.get_single_name();
if(test_name.size() == 3) {
test_name.erase(0, 1);
}
auto *op = get_option_no_throw(test_name);
if(op != nullptr) {
throw(OptionAlreadyAdded("added option positional name matches existing option: " + test_name));
}
} else if(parent_ != nullptr) {
for(auto &ln : myopt.lnames_) {
auto *op = parent_->get_option_no_throw(ln);
if(op != nullptr) {
throw(OptionAlreadyAdded("added option matches existing positional option: " + ln));
}
}
for(auto &sn : myopt.snames_) {
auto *op = parent_->get_option_no_throw(sn);
if(op != nullptr) {
throw(OptionAlreadyAdded("added option matches existing positional option: " + sn));
}
}
}
options_.emplace_back();
Option_p &option = options_.back();
option.reset(new Option(option_name, option_description, option_callback, this));
@ -371,13 +397,14 @@ CLI11_INLINE bool App::remove_option(Option *opt) {
CLI11_INLINE App *App::add_subcommand(std::string subcommand_name, std::string subcommand_description) {
if(!subcommand_name.empty() && !detail::valid_name_string(subcommand_name)) {
if(!detail::valid_first_char(subcommand_name[0])) {
throw IncorrectConstruction("Subcommand name starts with invalid character, '!' and '-' are not allowed");
throw IncorrectConstruction(
"Subcommand name starts with invalid character, '!' and '-' and control characters");
}
for(auto c : subcommand_name) {
if(!detail::valid_later_char(c)) {
throw IncorrectConstruction(std::string("Subcommand name contains invalid character ('") + c +
"'), all characters are allowed except"
"'=',':','{','}', and ' '");
"'=',':','{','}', ' ', and control characters");
}
}
}
@ -553,7 +580,6 @@ CLI11_INLINE void App::parse(std::string commandline, bool program_name_included
// remove all empty strings
args.erase(std::remove(args.begin(), args.end(), std::string{}), args.end());
std::reverse(args.begin(), args.end());
parse(std::move(args));
}
@ -723,7 +749,8 @@ CLI11_NODISCARD CLI11_INLINE std::string App::help(std::string prev, AppFormatMo
CLI11_NODISCARD CLI11_INLINE std::string App::version() const {
std::string val;
if(version_ptr_ != nullptr) {
auto rv = version_ptr_->results();
// copy the results for reuse later
results_t rv = version_ptr_->results();
version_ptr_->clear();
version_ptr_->add_result("true");
try {
@ -1395,12 +1422,11 @@ CLI11_INLINE void App::_parse_config(const std::vector<ConfigItem> &args) {
}
CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t level) {
if(level < item.parents.size()) {
try {
auto *subcom = get_subcommand(item.parents.at(level));
auto result = subcom->_parse_single_config(item, level + 1);
return result;
return subcom->_parse_single_config(item, level + 1);
} catch(const OptionNotFound &) {
return false;
}
@ -1430,10 +1456,11 @@ CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t
if(item.name.size() == 1) {
op = get_option_no_throw("-" + item.name);
}
}
if(op == nullptr) {
op = get_option_no_throw(item.name);
}
}
if(op == nullptr) {
// If the option was not present
if(get_allow_config_extras() == config_extras_mode::capture)
@ -1475,12 +1502,40 @@ CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t
op->add_result(res);
return true;
}
if(static_cast<int>(item.inputs.size()) > op->get_items_expected_max()) {
if(static_cast<int>(item.inputs.size()) > op->get_items_expected_max() &&
op->get_multi_option_policy() != MultiOptionPolicy::TakeAll) {
if(op->get_items_expected_max() > 1) {
throw ArgumentMismatch::AtMost(item.fullname(), op->get_items_expected_max(), item.inputs.size());
}
if(!op->get_disable_flag_override()) {
throw ConversionError::TooManyInputsFlag(item.fullname());
}
// if the disable flag override is set then we must have the flag values match a known flag value
// this is true regardless of the output value, so an array input is possible and must be accounted for
for(const auto &res : item.inputs) {
bool valid_value{false};
if(op->default_flag_values_.empty()) {
if(res == "true" || res == "false" || res == "1" || res == "0") {
valid_value = true;
}
} else {
for(const auto &valid_res : op->default_flag_values_) {
if(valid_res.second == res) {
valid_value = true;
break;
}
}
}
if(valid_value) {
op->add_result(res);
} else {
throw InvalidError("invalid flag argument given");
}
}
return true;
}
}
op->add_result(item.inputs);
op->run_callback();

View File

@ -19,11 +19,18 @@
namespace CLI {
// [CLI11:config_inl_hpp:verbatim]
static constexpr auto tquote = R"(""")";
static constexpr auto triple_quote = R"(""")";
namespace detail {
CLI11_INLINE std::string convert_arg_for_ini(const std::string &arg, char stringQuote, char characterQuote) {
CLI11_INLINE bool is_printable(const std::string &test_string) {
return std::all_of(test_string.begin(), test_string.end(), [](char x) {
return (isprint(static_cast<unsigned char>(x)) != 0 || x == '\n');
});
}
CLI11_INLINE std::string
convert_arg_for_ini(const std::string &arg, char stringQuote, char characterQuote, bool disable_multi_line) {
if(arg.empty()) {
return std::string(2, stringQuote);
}
@ -36,11 +43,22 @@ CLI11_INLINE std::string convert_arg_for_ini(const std::string &arg, char string
using CLI::detail::lexical_cast;
double val = 0.0;
if(lexical_cast(arg, val)) {
if(arg.find_first_not_of("0123456789.-+eE") == std::string::npos) {
return arg;
}
}
}
// just quote a single non numeric character
if(arg.size() == 1) {
if(isprint(static_cast<unsigned char>(arg.front())) == 0) {
return binary_escape_string(arg);
}
if(arg == "\\") {
return std::string(1, stringQuote) + "\\\\" + stringQuote;
}
if(arg == "'") {
return std::string(1, stringQuote) + "'" + stringQuote;
}
return std::string(1, characterQuote) + arg + characterQuote;
}
// handle hex, binary or octal arguments
@ -61,13 +79,19 @@ CLI11_INLINE std::string convert_arg_for_ini(const std::string &arg, char string
}
}
}
if(!is_printable(arg)) {
return binary_escape_string(arg);
}
if(arg.find_first_of('\n') != std::string::npos) {
return std::string(tquote) + arg + tquote;
if(disable_multi_line) {
return binary_escape_string(arg);
}
return std::string(triple_quote) + arg + triple_quote;
}
if(detail::has_escapable_character(arg)) {
return std::string(1, stringQuote) + detail::add_escaped_characters(arg) + stringQuote;
}
if(arg.find_first_of(stringQuote) == std::string::npos) {
return std::string(1, stringQuote) + arg + stringQuote;
}
return characterQuote + arg + characterQuote;
}
CLI11_INLINE std::string ini_join(const std::vector<std::string> &args,
@ -76,9 +100,11 @@ CLI11_INLINE std::string ini_join(const std::vector<std::string> &args,
char arrayEnd,
char stringQuote,
char characterQuote) {
bool disable_multi_line{false};
std::string joined;
if(args.size() > 1 && arrayStart != '\0') {
joined.push_back(arrayStart);
disable_multi_line = true;
}
std::size_t start = 0;
for(const auto &arg : args) {
@ -88,7 +114,7 @@ CLI11_INLINE std::string ini_join(const std::vector<std::string> &args,
joined.push_back(' ');
}
}
joined.append(convert_arg_for_ini(arg, stringQuote, characterQuote));
joined.append(convert_arg_for_ini(arg, stringQuote, characterQuote, disable_multi_line));
}
if(args.size() > 1 && arrayEnd != '\0') {
joined.push_back(arrayEnd);
@ -170,6 +196,7 @@ checkParentSegments(std::vector<ConfigItem> &output, const std::string &currentS
output.back().name = "++";
}
/// @brief checks if a string represents a multiline comment
CLI11_INLINE bool hasMLString(std::string const &fullString, char check) {
if(fullString.length() < 3) {
return false;
@ -190,6 +217,7 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons
bool inSection{false};
bool inMLineComment{false};
bool inMLineValue{false};
char aStart = (isINIArray) ? '[' : arrayStart;
char aEnd = (isINIArray) ? ']' : arrayEnd;
char aSep = (isINIArray && arraySeparator == ' ') ? ',' : arraySeparator;
@ -198,14 +226,14 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons
while(getline(input, buffer)) {
std::vector<std::string> items_buffer;
std::string name;
bool literalName{false};
line = detail::trim_copy(buffer);
std::size_t len = line.length();
// lines have to be at least 3 characters to have any meaning to CLI just skip the rest
if(len < 3) {
continue;
}
if(line.compare(0, 3, tquote) == 0 || line.compare(0, 3, "'''") == 0) {
if(line.compare(0, 3, triple_quote) == 0 || line.compare(0, 3, "'''") == 0) {
inMLineComment = true;
auto cchar = line.front();
while(inMLineComment) {
@ -249,20 +277,32 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons
// comment lines
if(line.front() == ';' || line.front() == '#' || line.front() == commentChar) {
if(line.compare(2, 13, "cli11:literal") == 0) {
literalName = true;
getline(input, buffer);
line = detail::trim_copy(buffer);
} else {
continue;
}
}
// Find = in string, split and recombine
auto delimiter_pos = line.find_first_of(valueDelimiter);
auto comment_pos = line.find_first_of(commentChar);
auto delimiter_pos = line.find_first_of(valueDelimiter, 1);
auto comment_pos = (literalName) ? std::string::npos : line.find_first_of(commentChar);
if(comment_pos < delimiter_pos) {
delimiter_pos = std::string::npos;
}
if(delimiter_pos != std::string::npos) {
name = detail::trim_copy(line.substr(0, delimiter_pos));
std::string item = detail::trim_copy(line.substr(delimiter_pos + 1, comment_pos - delimiter_pos - 1));
if(item.compare(0, 3, "'''") == 0 || item.compare(0, 3, tquote) == 0) {
std::string item = detail::trim_copy(line.substr(delimiter_pos + 1, std::string::npos));
bool mlquote = (item.compare(0, 3, "'''") == 0 || item.compare(0, 3, triple_quote) == 0);
if(!mlquote && comment_pos != std::string::npos && !literalName) {
auto citems = detail::split_up(item, commentChar, false);
item = detail::trim_copy(citems.front());
}
if(mlquote) {
// mutliline string
auto keyChar = item.front();
item = buffer.substr(delimiter_pos + 1, std::string::npos);
@ -318,11 +358,11 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons
detail::trim(multiline);
item += multiline;
}
items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep);
items_buffer = detail::split_up(item.substr(1, item.length() - 2), aSep, false);
} else if((isDefaultArray || isINIArray) && item.find_first_of(aSep) != std::string::npos) {
items_buffer = detail::split_up(item, aSep);
items_buffer = detail::split_up(item, aSep, false);
} else if((isDefaultArray || isINIArray) && item.find_first_of(' ') != std::string::npos) {
items_buffer = detail::split_up(item);
items_buffer = detail::split_up(item, '\0', false);
} else {
items_buffer = {item};
}
@ -331,14 +371,24 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons
items_buffer = {"true"};
}
if(name.find(parentSeparatorChar) == std::string::npos) {
if(!literalName) {
detail::remove_quotes(name);
}
// clean up quotes on the items
}
// clean up quotes on the items and check for escaped strings
for(auto &it : items_buffer) {
detail::remove_quotes(it);
if(detail::is_binary_escaped_string(it)) {
it = detail::extract_binary_string(it);
}
}
std::vector<std::string> parents;
if(literalName) {
std::string noname{};
parents = detail::generate_parents(currentSection, noname, parentSeparatorChar);
} else {
parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
}
std::vector<std::string> parents = detail::generate_parents(currentSection, name, parentSeparatorChar);
if(parents.size() > maximumLayers) {
continue;
}
@ -382,6 +432,10 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
commentLead.push_back(commentChar);
commentLead.push_back(' ');
std::string commentTest = "#;";
commentTest.push_back(commentChar);
commentTest.push_back(parentSeparatorChar);
std::vector<std::string> groups = app->get_groups();
bool defaultUsed = false;
groups.insert(groups.begin(), std::string("Options"));
@ -408,12 +462,15 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
}
}
std::string name = prefix + opt->get_single_name();
if(name == prefix) {
continue;
}
std::string value = detail::ini_join(
opt->reduced_results(), arraySeparator, arrayStart, arrayEnd, stringQuote, characterQuote);
if(value.empty() && default_also) {
if(!opt->get_default_str().empty()) {
value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, characterQuote);
value = detail::convert_arg_for_ini(opt->get_default_str(), stringQuote, characterQuote, false);
} else if(opt->get_expected_min() == 0) {
value = "false";
} else if(opt->get_run_callback_for_default()) {
@ -423,12 +480,36 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
if(!value.empty()) {
if(!opt->get_fnames().empty()) {
try {
value = opt->get_flag_value(name, value);
} catch(const CLI::ArgumentMismatch &) {
bool valid{false};
for(const auto &test_name : opt->get_fnames()) {
try {
value = opt->get_flag_value(test_name, value);
name = test_name;
valid = true;
} catch(const CLI::ArgumentMismatch &) {
continue;
}
}
if(!valid) {
value = detail::ini_join(
opt->results(), arraySeparator, arrayStart, arrayEnd, stringQuote, characterQuote);
}
}
}
if(write_description && opt->has_description()) {
out << '\n';
out << commentLead << detail::fix_newlines(commentLead, opt->get_description()) << '\n';
}
if(name.find_first_of(commentTest) != std::string::npos || name.compare(0, 3, triple_quote) == 0 ||
name.compare(0, 3, "'''") == 0 || (name.front() == '[' && name.back() == ']') ||
(name.front() == stringQuote && name.back() == stringQuote) ||
(name.front() == characterQuote && name.back() == characterQuote) ||
(name.front() == '`' && name.back() == '`')) {
out << commentChar << " cli11:literal\n";
}
out << name << valueDelimiter << value << '\n';
}
}
@ -437,15 +518,33 @@ ConfigBase::to_config(const App *app, bool default_also, bool write_description,
auto subcommands = app->get_subcommands({});
for(const App *subcom : subcommands) {
if(subcom->get_name().empty()) {
if(!default_also && (subcom->count_all() == 0)) {
continue;
}
if(write_description && !subcom->get_group().empty()) {
out << '\n' << commentLead << subcom->get_group() << " Options\n";
}
/*if (!prefix.empty() || app->get_parent() == nullptr) {
out << '[' << prefix << "___"<< subcom->get_group() << "]\n";
} else {
std::string subname = app->get_name() + parentSeparatorChar + "___"+subcom->get_group();
const auto *p = app->get_parent();
while(p->get_parent() != nullptr) {
subname = p->get_name() + parentSeparatorChar +subname;
p = p->get_parent();
}
out << '[' << subname << "]\n";
}
*/
out << to_config(subcom, default_also, write_description, prefix);
}
}
for(const App *subcom : subcommands) {
if(!subcom->get_name().empty()) {
if(!default_also && (subcom->count_all() == 0)) {
continue;
}
if(subcom->get_configurable() && app->got_subcommand(subcom)) {
if(!prefix.empty() || app->get_parent() == nullptr) {
out << '[' << prefix << subcom->get_name() << "]\n";

View File

@ -309,13 +309,29 @@ CLI11_INLINE void Option::run_callback() {
CLI11_NODISCARD CLI11_INLINE const std::string &Option::matching_name(const Option &other) const {
static const std::string estring;
for(const std::string &sname : snames_)
for(const std::string &sname : snames_) {
if(other.check_sname(sname))
return sname;
for(const std::string &lname : lnames_)
if(other.check_lname(sname))
return sname;
}
for(const std::string &lname : lnames_) {
if(other.check_lname(lname))
return lname;
if(lname.size() == 1) {
if(other.check_sname(lname)) {
return lname;
}
}
}
if(snames_.empty() && lnames_.empty() && !pname_.empty()) {
if(other.check_sname(pname_) || other.check_lname(pname_) || pname_ == other.pname_)
return pname_;
}
if(other.snames_.empty() && other.fnames_.empty() && !other.pname_.empty()) {
if(check_sname(other.pname_) || check_lname(other.pname_) || (pname_ == other.pname_))
return other.pname_;
}
if(ignore_case_ ||
ignore_underscore_) { // We need to do the inverse, in case we are ignore_case or ignore underscore
for(const std::string &sname : other.snames_)
@ -369,6 +385,9 @@ CLI11_NODISCARD CLI11_INLINE std::string Option::get_flag_value(const std::strin
if(default_ind >= 0) {
// We can static cast this to std::size_t because it is more than 0 in this block
if(default_flag_values_[static_cast<std::size_t>(default_ind)].second != input_value) {
if(input_value == default_str_ && force_callback_) {
return input_value;
}
throw(ArgumentMismatch::FlagOverride(name));
}
} else {

View File

@ -106,7 +106,6 @@ get_names(const std::vector<std::string> &input) {
std::vector<std::string> short_names;
std::vector<std::string> long_names;
std::string pos_name;
for(std::string name : input) {
if(name.length() == 0) {
continue;
@ -127,12 +126,15 @@ get_names(const std::vector<std::string> &input) {
} else if(name == "-" || name == "--") {
throw BadNameString::DashesOnly(name);
} else {
if(pos_name.length() > 0)
if(!pos_name.empty())
throw BadNameString::MultiPositionalNames(name);
if(valid_name_string(name)) {
pos_name = name;
} else {
throw BadNameString::BadPositionalName(name);
}
}
}
return std::make_tuple(short_names, long_names, pos_name);
}

View File

@ -11,6 +11,7 @@
// [CLI11:public_includes:set]
#include <string>
#include <utility>
#include <vector>
// [CLI11:public_includes:end]
@ -180,9 +181,79 @@ find_member(std::string name, const std::vector<std::string> names, bool ignore_
return (it != std::end(names)) ? (it - std::begin(names)) : (-1);
}
CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter) {
static const std::string escapedChars("'\"`])>}\\");
static const std::string bracketChars{"'\"`[(<{"};
static const std::string matchBracketChars("'\"`])>}");
CLI11_INLINE bool has_escapable_character(const std::string &str) {
return (str.find_first_of(escapedChars) != std::string::npos);
}
CLI11_INLINE std::string add_escaped_characters(const std::string &str) {
std::string out;
out.reserve(str.size() + 4);
for(char s : str) {
if(escapedChars.find_first_of(s) != std::string::npos) {
out.push_back('\\');
}
out.push_back(s);
}
return out;
}
CLI11_INLINE std::string remove_escaped_characters(const std::string &str) {
std::string out;
out.reserve(str.size());
for(auto loc = str.begin(); loc < str.end(); ++loc) {
if(*loc == '\\') {
if(escapedChars.find_first_of(*(loc + 1)) != std::string::npos) {
out.push_back(*(loc + 1));
++loc;
} else {
out.push_back(*loc);
}
} else {
out.push_back(*loc);
}
}
return out;
}
CLI11_INLINE std::pair<std::size_t, bool> close_sequence(const std::string &str, std::size_t start, char closure_char) {
std::string closures;
closures.push_back(closure_char);
auto loc = start + 1;
bool inQuote = closure_char == '"' || closure_char == '\'' || closure_char == '`';
bool hasControlSequence{false};
while(loc < str.size()) {
if(str[loc] == closures.back()) {
closures.pop_back();
if(closures.empty()) {
return {loc, hasControlSequence};
}
inQuote = false;
}
if(str[loc] == '\\') {
if(inQuote) {
hasControlSequence = true;
}
++loc;
}
if(!inQuote) {
auto bracket_loc = bracketChars.find(str[loc]);
if(bracket_loc != std::string::npos) {
closures.push_back(matchBracketChars[bracket_loc]);
inQuote = (bracket_loc <= 2);
}
}
++loc;
}
return {loc, hasControlSequence};
}
CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter, bool removeQuotes) {
const std::string delims("\'\"`");
auto find_ws = [delimiter](char ch) {
return (delimiter == '\0') ? std::isspace<char>(ch, std::locale()) : (ch == delimiter);
};
@ -190,27 +261,19 @@ CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter)
std::vector<std::string> output;
bool embeddedQuote = false;
char keyChar = ' ';
std::size_t adjust = removeQuotes ? 1 : 0;
while(!str.empty()) {
if(delims.find_first_of(str[0]) != std::string::npos) {
keyChar = str[0];
auto end = str.find_first_of(keyChar, 1);
while((end != std::string::npos) && (str[end - 1] == '\\')) { // deal with escaped quotes
end = str.find_first_of(keyChar, end + 1);
embeddedQuote = true;
}
if(end != std::string::npos) {
output.push_back(str.substr(1, end - 1));
if(bracketChars.find_first_of(str[0]) != std::string::npos) {
auto bracketLoc = bracketChars.find_first_of(str[0]);
auto closure = close_sequence(str, 0, matchBracketChars[bracketLoc]);
auto end = closure.first;
output.push_back(str.substr(adjust, end + 1 - 2 * adjust));
if(end + 2 < str.size()) {
str = str.substr(end + 2);
} else {
str.clear();
}
} else {
output.push_back(str.substr(1));
str = "";
}
embeddedQuote = embeddedQuote || closure.second;
} else {
auto it = std::find_if(std::begin(str), std::end(str), find_ws);
if(it != std::end(str)) {
@ -219,12 +282,12 @@ CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter)
str = std::string(it + 1, str.end());
} else {
output.push_back(str);
str = "";
str.clear();
}
}
// transform any embedded quotes into the regular character
if(embeddedQuote) {
output.back() = find_and_replace(output.back(), std::string("\\") + keyChar, std::string(1, keyChar));
// transform any embedded quotes into the regular character if the quotes are removed
if(embeddedQuote && removeQuotes) {
output.back() = remove_escaped_characters(output.back());
embeddedQuote = false;
}
trim(str);
@ -244,6 +307,105 @@ CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset) {
return offset + 1;
}
CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape) {
// s is our escaped output string
std::string escaped_string{};
// loop through all characters
for(char c : string_to_escape) {
// check if a given character is printable
// the cast is necessary to avoid undefined behaviour
if(isprint(static_cast<unsigned char>(c)) == 0) {
std::stringstream stream;
// if the character is not printable
// we'll convert it to a hex string using a stringstream
// note that since char is signed we have to cast it to unsigned first
stream << std::hex << static_cast<unsigned int>(static_cast<unsigned char>(c));
std::string code = stream.str();
escaped_string += std::string("\\x") + (code.size() < 2 ? "0" : "") + code;
} else {
escaped_string.push_back(c);
}
}
if(escaped_string != string_to_escape) {
auto sqLoc = escaped_string.find('\'');
while(sqLoc != std::string::npos) {
escaped_string.replace(sqLoc, sqLoc + 1, "\\x27");
sqLoc = escaped_string.find('\'');
}
escaped_string.insert(0, "'B\"(");
escaped_string.push_back(')');
escaped_string.push_back('"');
escaped_string.push_back('\'');
}
return escaped_string;
}
CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string) {
size_t ssize = escaped_string.size();
if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) {
return true;
}
return (escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0);
}
CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string) {
std::size_t start{0};
std::size_t tail{0};
size_t ssize = escaped_string.size();
if(escaped_string.compare(0, 3, "B\"(") == 0 && escaped_string.compare(ssize - 2, 2, ")\"") == 0) {
start = 3;
tail = 2;
} else if(escaped_string.compare(0, 4, "'B\"(") == 0 && escaped_string.compare(ssize - 3, 3, ")\"'") == 0) {
start = 4;
tail = 3;
}
if(start == 0) {
return escaped_string;
}
std::string outstring;
outstring.reserve(ssize - start - tail);
std::size_t loc = start;
while(loc < ssize - tail) {
// ssize-2 to skip )" at the end
if(escaped_string[loc] == '\\' && (escaped_string[loc + 1] == 'x' || escaped_string[loc + 1] == 'X')) {
auto c1 = escaped_string[loc + 2];
auto c2 = escaped_string[loc + 3];
int res{0};
bool invalid{false};
if(c1 >= '0' && c1 <= '9') {
res = (c1 - '0') * 16;
} else if(c1 >= 'A' && c1 <= 'F') {
res = (c1 - 'A' + 10) * 16;
} else if(c1 >= 'a' && c1 <= 'f') {
res = (c1 - 'a' + 10) * 16;
} else {
invalid = true;
}
if(c2 >= '0' && c2 <= '9') {
res += (c2 - '0');
} else if(c2 >= 'A' && c2 <= 'F') {
res += (c2 - 'A' + 10);
} else if(c2 >= 'a' && c2 <= 'f') {
res += (c2 - 'a' + 10);
} else {
invalid = true;
}
if(!invalid) {
loc += 4;
outstring.push_back(static_cast<char>(res));
continue;
}
}
outstring.push_back(escaped_string[loc]);
++loc;
}
return outstring;
}
std::string get_environment_value(const std::string &env_name) {
char *buffer = nullptr;
std::string ename_string;

View File

@ -80,28 +80,6 @@ TEST_CASE_METHOD(TApp, "OneFlagShortWindows", "[app]") {
CHECK(app.count("--count") == 1u);
}
TEST_CASE_METHOD(TApp, "WindowsLongShortMix1", "[app]") {
app.allow_windows_style_options();
auto *a = app.add_flag("-c");
auto *b = app.add_flag("--c");
args = {"/c"};
run();
CHECK(a->count() == 1u);
CHECK(b->count() == 0u);
}
TEST_CASE_METHOD(TApp, "WindowsLongShortMix2", "[app]") {
app.allow_windows_style_options();
auto *a = app.add_flag("--c");
auto *b = app.add_flag("-c");
args = {"/c"};
run();
CHECK(a->count() == 1u);
CHECK(b->count() == 0u);
}
TEST_CASE_METHOD(TApp, "CountNonExist", "[app]") {
app.add_flag("-c,--count");
args = {"-c"};

View File

@ -1,4 +1,4 @@
if(CLI11_SANITIZERS)
if(CLI11_SANITIZERS AND ${CMAKE_VERSION} VERSION_GREATER "3.13.0")
message(STATUS "Using arsenm/sanitizers-cmake")
FetchContent_Declare(
sanitizers

View File

@ -2018,6 +2018,41 @@ TEST_CASE_METHOD(TApp, "IniNotConfigurable", "[config]") {
CHECK_NOTHROW(run());
}
TEST_CASE_METHOD(TApp, "IniFlagDisableOverrideFlagArray", "[config]") {
TempFile tmpini{"TestIniTmp.ini"};
app.set_config("--config", tmpini);
int value{0};
app.add_flag("--val", value)->configurable(true)->disable_flag_override();
{
std::ofstream out{tmpini};
out << "[default]" << std::endl;
out << "val=[1,true,false,true]" << std::endl;
}
REQUIRE_NOTHROW(run());
CHECK(value == 2);
}
TEST_CASE_METHOD(TApp, "IniFlagInvalidDisableOverrideFlagArray", "[config]") {
TempFile tmpini{"TestIniTmp.ini"};
app.set_config("--config", tmpini);
int value{0};
app.add_flag("--val", value)->configurable(true)->disable_flag_override();
{
std::ofstream out{tmpini};
out << "[default]" << std::endl;
out << "val=[1,true,false,not_valid]" << std::endl;
}
CHECK_THROWS_AS(run(), CLI::InvalidError);
}
TEST_CASE_METHOD(TApp, "IniSubFailure", "[config]") {
TempFile tmpini{"TestIniTmp.ini"};
@ -2666,6 +2701,7 @@ TEST_CASE_METHOD(TApp, "TomlOutputOptionGroupMultiLineDescription", "[config]")
og->description("Option group description.\n"
"That has multiple lines.");
og->add_flag("--" + flag, description);
args = {"--" + flag};
run();
std::string str = app.config_to_str(true, true);
@ -2955,7 +2991,7 @@ TEST_CASE_METHOD(TApp, "TomlOutputQuoted", "[config]") {
std::string str = app.config_to_str();
CHECK_THAT(str, Contains("val1=\"I am a string\""));
CHECK_THAT(str, Contains("val2='I am a \"confusing\" string'"));
CHECK_THAT(str, Contains("val2=\"I am a \\\"confusing\\\" string\""));
}
TEST_CASE_METHOD(TApp, "DefaultsTomlOutputQuoted", "[config]") {
@ -2970,7 +3006,7 @@ TEST_CASE_METHOD(TApp, "DefaultsTomlOutputQuoted", "[config]") {
std::string str = app.config_to_str(true);
CHECK_THAT(str, Contains("val1=\"I am a string\""));
CHECK_THAT(str, Contains("val2='I am a \"confusing\" string'"));
CHECK_THAT(str, Contains("val2=\"I am a \\\"confusing\\\" string\""));
}
// #298
@ -3421,7 +3457,7 @@ TEST_CASE_METHOD(TApp, "IniOutputQuoted", "[config]") {
std::string str = app.config_to_str();
CHECK_THAT(str, Contains("val1=\"I am a string\""));
CHECK_THAT(str, Contains("val2='I am a \"confusing\" string'"));
CHECK_THAT(str, Contains("val2=\"I am a \\\"confusing\\\" string\""));
}
TEST_CASE_METHOD(TApp, "DefaultsIniOutputQuoted", "[config]") {
@ -3436,5 +3472,5 @@ TEST_CASE_METHOD(TApp, "DefaultsIniOutputQuoted", "[config]") {
std::string str = app.config_to_str(true);
CHECK_THAT(str, Contains("val1=\"I am a string\""));
CHECK_THAT(str, Contains("val2='I am a \"confusing\" string'"));
CHECK_THAT(str, Contains("val2=\"I am a \\\"confusing\\\" string\""));
}

View File

@ -40,6 +40,16 @@ TEST_CASE_METHOD(TApp, "AddingExistingWithCase", "[creation]") {
CHECK_NOTHROW(app.add_flag("--Cat,-C"));
}
TEST_CASE_METHOD(TApp, "AddingExistingShortLong", "[creation]") {
app.add_flag("-c");
CHECK_THROWS_AS(app.add_flag("--c"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "AddingExistingLongShort", "[creation]") {
app.add_flag("--c");
CHECK_THROWS_AS(app.add_option("-c"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "AddingExistingWithCaseAfter", "[creation]") {
auto *count = app.add_flag("-c,--count");
app.add_flag("--Cat,-C");
@ -68,6 +78,37 @@ TEST_CASE_METHOD(TApp, "AddingExistingWithUnderscoreAfter2", "[creation]") {
CHECK_THROWS_AS(count->ignore_underscore(), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "matchPositional", "[creation]") {
app.add_option("firstoption");
CHECK_THROWS_AS(app.add_option("--firstoption"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "matchPositional2", "[creation]") {
app.add_option("--firstoption");
CHECK_THROWS_AS(app.add_option("firstoption"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "matchPositionalInOptionGroup1", "[creation]") {
auto *g1 = app.add_option_group("group_b");
g1->add_option("--firstoption");
CHECK_THROWS_AS(app.add_option("firstoption"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "matchPositionalInOptionGroup2", "[creation]") {
app.add_option("firstoption");
auto *g1 = app.add_option_group("group_b");
CHECK_THROWS_AS(g1->add_option("--firstoption"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "matchPositionalInOptionGroup3", "[creation]") {
app.add_option("f");
auto *g1 = app.add_option_group("group_b");
CHECK_THROWS_AS(g1->add_option("-f"), CLI::OptionAlreadyAdded);
}
TEST_CASE_METHOD(TApp, "AddingMultipleInfPositionals", "[creation]") {
std::vector<std::string> one, two;
app.add_option("one", one);

View File

@ -58,3 +58,40 @@ TEST_CASE("file_fail") {
} catch(const CLI::ParseError & /*e*/) {
}
}
TEST_CASE("app_file_gen_fail") {
CLI::FuzzApp fuzzdata;
auto app = fuzzdata.generateApp();
int index = GENERATE(range(1, 33));
std::string optionString, flagString;
auto parseData = loadFailureFile("fuzz_app_file_fail", index);
if(parseData.size() > 25) {
optionString = parseData.substr(0, 25);
parseData.erase(0, 25);
}
if(parseData.size() > 25) {
flagString = parseData.substr(0, 25);
parseData.erase(0, 25);
}
try {
if(!optionString.empty()) {
app->add_option(optionString, fuzzdata.buffer);
}
if(!flagString.empty()) {
app->add_flag(flagString, fuzzdata.intbuffer);
}
try {
app->parse(parseData);
} catch(const CLI::ParseError & /*e*/) {
return;
}
} catch(const CLI::ConstructionError & /*e*/) {
return;
}
std::string configOut = app->config_to_str();
app->clear();
std::stringstream out(configOut);
app->parse_from_stream(out);
}

View File

@ -237,6 +237,77 @@ TEST_CASE("StringTools: Validation", "[helpers]") {
CHECK_FALSE(CLI::detail::isalpha("test2"));
}
TEST_CASE("StringTools: binaryEscapseConversion", "[helpers]") {
std::string testString("string1");
std::string estring = CLI::detail::binary_escape_string(testString);
CHECK(testString == estring);
CHECK_FALSE(CLI::detail::is_binary_escaped_string(estring));
std::string testString2("\nstring1\n");
estring = CLI::detail::binary_escape_string(testString2);
CHECK_FALSE(testString == estring);
CHECK(CLI::detail::is_binary_escaped_string(estring));
std::string rstring = CLI::detail::extract_binary_string(estring);
CHECK(rstring == testString2);
testString2.push_back(0);
testString2.push_back(static_cast<char>(197));
testString2.push_back(78);
testString2.push_back(-34);
rstring = CLI::detail::extract_binary_string(CLI::detail::binary_escape_string(testString2));
CHECK(rstring == testString2);
testString2.push_back('b');
testString2.push_back('G');
rstring = CLI::detail::extract_binary_string(CLI::detail::binary_escape_string(testString2));
CHECK(rstring == testString2);
auto rstring2 = CLI::detail::extract_binary_string(rstring);
CHECK(rstring == rstring2);
}
TEST_CASE("StringTools: binaryStrings", "[helpers]") {
std::string rstring = "B\"()\"";
CHECK(CLI::detail::extract_binary_string(rstring).empty());
rstring = "B\"(\\x35\\xa7)\"";
auto result = CLI::detail::extract_binary_string(rstring);
CHECK(result[0] == static_cast<char>(0x35));
CHECK(result[1] == static_cast<char>(0xa7));
rstring = "B\"(\\x3e\\xf7)\"";
result = CLI::detail::extract_binary_string(rstring);
CHECK(result[0] == static_cast<char>(0x3e));
CHECK(result[1] == static_cast<char>(0xf7));
rstring = "B\"(\\x3E\\xf7)\"";
result = CLI::detail::extract_binary_string(rstring);
CHECK(result[0] == static_cast<char>(0x3e));
CHECK(result[1] == static_cast<char>(0xf7));
rstring = "B\"(\\X3E\\XF7)\"";
result = CLI::detail::extract_binary_string(rstring);
CHECK(result[0] == static_cast<char>(0x3e));
CHECK(result[1] == static_cast<char>(0xf7));
rstring = "B\"(\\XME\\XK7)\"";
result = CLI::detail::extract_binary_string(rstring);
CHECK(result == "\\XME\\XK7");
rstring = "B\"(\\XEM\\X7K)\"";
result = CLI::detail::extract_binary_string(rstring);
CHECK(result == "\\XEM\\X7K");
}
TEST_CASE("StringTools: escapeConversion", "[helpers]") {
CHECK(CLI::detail::remove_escaped_characters("test\\\"") == "test\"");
CHECK(CLI::detail::remove_escaped_characters("test\\}") == "test}");
CHECK(CLI::detail::remove_escaped_characters("test\\\\") == "test\\");
CHECK(CLI::detail::remove_escaped_characters("test\\\\") == "test\\");
CHECK(CLI::detail::remove_escaped_characters("test\\k") == "test\\k");
}
TEST_CASE("Trim: Various", "[helpers]") {
std::string s1{" sdlfkj sdflk sd s "};
std::string a1{"sdlfkj sdflk sd s"};
@ -909,6 +980,20 @@ TEST_CASE("SplitUp: SimpleDifferentQuotes", "[helpers]") {
CHECK(result == oput);
}
TEST_CASE("SplitUp: SimpleMissingQuotes", "[helpers]") {
std::vector<std::string> oput = {"one", "two three"};
std::string orig{R"(one `two three)"};
std::vector<std::string> result = CLI::detail::split_up(orig);
CHECK(result == oput);
}
TEST_CASE("SplitUp: SimpleMissingQuotesEscaped", "[helpers]") {
std::vector<std::string> oput = {"one", "two three`"};
std::string orig{R"(one `two three\`)"};
std::vector<std::string> result = CLI::detail::split_up(orig);
CHECK(result == oput);
}
TEST_CASE("SplitUp: SimpleDifferentQuotes2", "[helpers]") {
std::vector<std::string> oput = {"one", "two three"};
std::string orig{R"(one 'two three')"};
@ -916,6 +1001,41 @@ TEST_CASE("SplitUp: SimpleDifferentQuotes2", "[helpers]") {
CHECK(result == oput);
}
TEST_CASE("SplitUp: Bracket1", "[helpers]") {
std::vector<std::string> oput = {"one", "[two, three]"};
std::string orig{"one, [two, three]"};
std::vector<std::string> result = CLI::detail::split_up(orig, ',', false);
CHECK(result == oput);
}
TEST_CASE("SplitUp: Bracket2", "[helpers]") {
std::vector<std::string> oput = {"one", "<two, three>"};
std::string orig{"one, <two, three>"};
std::vector<std::string> result = CLI::detail::split_up(orig, ',', false);
CHECK(result == oput);
}
TEST_CASE("SplitUp: Bracket3", "[helpers]") {
std::vector<std::string> oput = {"one", "(two, three)"};
std::string orig{"one, (two, three)"};
std::vector<std::string> result = CLI::detail::split_up(orig, ',', false);
CHECK(result == oput);
}
TEST_CASE("SplitUp: Bracket4", "[helpers]") {
std::vector<std::string> oput = {"one", "{two, three}"};
std::string orig{"one, {two, three}"};
std::vector<std::string> result = CLI::detail::split_up(orig, ',', false);
CHECK(result == oput);
}
TEST_CASE("SplitUp: Comment", "[helpers]") {
std::vector<std::string> oput = {R"(["quote1", "#"])"};
std::string orig{R"(["quote1", "#"])"};
std::vector<std::string> result = CLI::detail::split_up(orig, '#', false);
CHECK(result == oput);
}
TEST_CASE("SplitUp: Layered", "[helpers]") {
std::vector<std::string> output = {R"(one 'two three')"};
std::string orig{R"("one 'two three'")"};

View File

View File

@ -0,0 +1,3 @@
-e-vC
,cÌC
,cÌÛÑÒ

View File

@ -0,0 +1 @@
=666666666~5Ë5Ë--oo?ptvtup@

Binary file not shown.

View File

@ -0,0 +1 @@
``'``'######################

View File

@ -0,0 +1,4 @@
--vB
s
'
sub

View File

@ -0,0 +1 @@
""K<><4B><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>"鰫

Binary file not shown.

View File

@ -0,0 +1,9 @@
--vE
˙˙˙˙˙
#

View File

@ -0,0 +1 @@
--vD \<5C><EFBFBD><7F><EFBFBD> \

View File

@ -0,0 +1 @@
1-!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!ceeecae

View File

@ -0,0 +1 @@
-c

View File

@ -0,0 +1 @@
································-é#e,cecb

Binary file not shown.

View File

@ -0,0 +1 @@
dwrap 'a

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
--vC opCB (3tp"o3#

View File

@ -0,0 +1,2 @@
--vD <0C> `
-5

View File

@ -0,0 +1,5 @@
--vB
--vB
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>,<2C><><EFBFBD><EFBFBD>-vC

View File

@ -0,0 +1,4 @@
--vE
-3vE
0)-bb=`',,l

View File

@ -0,0 +1 @@
`--vM```-````-c`

View File

@ -0,0 +1 @@
[驎驎驎驎驎驎珉ppt1"wrappt1""\","""\""\","

View File

@ -0,0 +1 @@
--$,,,,,,,,,,,,,,,,,,,,A,,,,,,,,,,,-$,,,,,,,,,,,,,,,,,,,,A,,,,,,,,,,,,,,,,,,,,,;--svopt2#,,,,-sC

View File

@ -0,0 +1 @@
-¬,,,,,,,,,,,,,,,,,,,opt1śa

View File

@ -0,0 +1 @@
-ccaaaa

View File

@ -0,0 +1,6 @@
Š
atd
ŸVVV-baÒî=
î
<EFBFBD>
.-' -

Binary file not shown.

View File

@ -0,0 +1,3 @@
.br-bN3CLI10ParseErrorEa5

Binary file not shown.

View File

@ -0,0 +1 @@
=o˙˙˙˙˙˙˙˙˙˙p--2v˙˙t ' Í-č-