Skip to content

Commit 0f5bf21

Browse files
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>
1 parent 3bc2739 commit 0f5bf21

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

58 files changed

+846
-114
lines changed

Diff for: .codacy.yml

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
engines:
3+
rubocop:
4+
enabled: true
5+
duplication:
6+
enabled: true
7+
metrics:
8+
enabled: true
9+
coverage:
10+
enabled: false
11+
languages:
12+
13+
exclude_paths:
14+
- "fuzz/**/*"
15+
- "fuzz/*"
16+
- "scripts/**/*"
17+
- "scripts/*"
18+
- "**.md"

Diff for: CPPLINT.cfg

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

Diff for: book/chapters/config.md

+10
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,16 @@ str3 = """\
206206
The key is that the closing of the multiline string must be at the end of a line
207207
and match the starting 3 quote sequence.
208208

209+
### Binary Strings
210+
211+
Config files have a binary conversion capability, this is mainly to support
212+
writing config files but can be used by user generated files as well. Strings
213+
with the form `B"(XXXXX)"` will convert any characters inside the parenthesis
214+
with the form \xHH to the equivalent binary value. The HH are hexadecimal
215+
characters. Characters not in this form will be translated as given. If argument
216+
values with unprintable characters are used to generate a config file this
217+
binary form will be used in the output string.
218+
209219
## Multiple configuration files
210220

211221
If it is desired that multiple configuration be allowed. Use

Diff for: fuzz/cli11_app_fuzz.cpp

+8-2
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,16 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
4040

4141
try {
4242
app->parse(parseString);
43+
4344
} catch(const CLI::ParseError &e) {
4445
//(app)->exit(e);
4546
// this just indicates we caught an error known by CLI
47+
return 0; // Non-zero return values are reserved for future use.
4648
}
47-
48-
return 0; // Non-zero return values are reserved for future use.
49+
// should be able to write the config to a file and read from it again
50+
std::string configOut = app->config_to_str();
51+
app->clear();
52+
std::stringstream out(configOut);
53+
app->parse_from_stream(out);
54+
return 0;
4955
}

Diff for: fuzz/cli11_file_fuzz.cpp

+7
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,13 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
2222
auto app = fuzzdata.generateApp();
2323
try {
2424
app->parse_from_stream(out);
25+
// should be able to write the config to a file and read from it again
26+
std::string configOut = app->config_to_str();
27+
28+
app->clear();
29+
std::stringstream out(configOut);
30+
app->parse_from_stream(out);
31+
2532
} catch(const CLI::ParseError &e) {
2633
// (app)->exit(e);
2734
// this just indicates we caught an error known by CLI

Diff for: fuzz/fuzzApp.cpp

+26-2
Original file line numberDiff line numberDiff line change
@@ -68,9 +68,9 @@ std::shared_ptr<CLI::App> FuzzApp::generateApp() {
6868
auto *vgroup = fApp->add_option_group("vectors");
6969

7070
vgroup->add_option("--vopt1", vv1);
71-
vgroup->add_option("--vopt2", vvs);
71+
vgroup->add_option("--vopt2", vvs)->inject_separator();
7272
vgroup->add_option("--vopt3", vstr);
73-
vgroup->add_option("--vopt4", vecvecd);
73+
vgroup->add_option("--vopt4", vecvecd)->inject_separator();
7474

7575
fApp->add_option("--oopt1", od1);
7676
fApp->add_option("--oopt2", ods);
@@ -121,6 +121,30 @@ std::shared_ptr<CLI::App> FuzzApp::generateApp() {
121121
sub->add_option("--sdwrap", dwrap);
122122
sub->add_option("--siwrap", iwrap);
123123

124+
auto *resgroup = fApp->add_option_group("outputOrder");
125+
126+
resgroup->add_option("--vA", vstrA)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::TakeAll);
127+
resgroup->add_option("--vB", vstrB)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::TakeLast);
128+
resgroup->add_option("--vC", vstrC)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::TakeFirst);
129+
resgroup->add_option("--vD", vstrD)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::Reverse);
130+
resgroup->add_option("--vS", val32)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::Sum);
131+
resgroup->add_option("--vM", mergeBuffer)->expected(0, 2)->multi_option_policy(CLI::MultiOptionPolicy::Join);
132+
resgroup->add_option("--vE", vstrE)->expected(2, 4)->delimiter(',');
133+
134+
auto *vldtr = fApp->add_option_group("validators");
135+
136+
validator_strings.resize(10);
137+
vldtr->add_option("--vdtr1", validator_strings[0])->join()->check(CLI::PositiveNumber);
138+
vldtr->add_option("--vdtr2", validator_strings[1])->join()->check(CLI::NonNegativeNumber);
139+
vldtr->add_option("--vdtr3", validator_strings[2])->join()->check(CLI::NonexistentPath);
140+
vldtr->add_option("--vdtr4", validator_strings[3])->join()->check(CLI::Range(7, 3456));
141+
vldtr->add_option("--vdtr5", validator_strings[4])
142+
->join()
143+
->check(CLI::Range(std::string("aa"), std::string("zz"), "string range"));
144+
vldtr->add_option("--vdtr6", validator_strings[5])->join()->check(CLI::TypeValidator<double>());
145+
vldtr->add_option("--vdtr7", validator_strings[6])->join()->check(CLI::TypeValidator<bool>());
146+
vldtr->add_option("--vdtr8", validator_strings[7])->join()->check(CLI::ValidIPV4);
147+
vldtr->add_option("--vdtr9", validator_strings[8])->join()->transform(CLI::Bound(2, 255));
124148
return fApp;
125149
}
126150

Diff for: fuzz/fuzzApp.hpp

+11
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ class FuzzApp {
7575

7676
std::vector<double> vv1{};
7777
std::vector<std::string> vstr{};
78+
7879
std::vector<std::vector<double>> vecvecd{};
7980
std::vector<std::vector<std::string>> vvs{};
8081
std::optional<double> od1{};
@@ -103,5 +104,15 @@ class FuzzApp {
103104
std::string buffer{};
104105
int intbuffer{0};
105106
std::atomic<double> doubleAtomic{0.0};
107+
108+
// for testing restrictions and reduction methods
109+
std::vector<std::string> vstrA{};
110+
std::vector<std::string> vstrB{};
111+
std::vector<std::string> vstrC{};
112+
std::vector<std::string> vstrD{};
113+
std::vector<std::string> vstrE{};
114+
std::vector<std::string> vstrF{};
115+
std::string mergeBuffer{};
116+
std::vector<std::string> validator_strings{};
106117
};
107118
} // namespace CLI

Diff for: fuzz/fuzzCommand.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ int main(int argc, char **argv) {
2020
(app)->exit(e);
2121
// this just indicates we caught an error known by CLI
2222
}
23+
2324
return 0;
2425
}

Diff for: fuzz/fuzz_dictionary1.txt

+16
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@
6666
"--siwrap"
6767
"--svtup"
6868
"--satd"
69+
"--vA"
70+
"--vB"
71+
"--vC"
72+
"--vD"
73+
"--vS"
74+
"--vM"
75+
"--vE"
76+
"--vdtr"
6977
"nflag2"
7078
"stup1"
7179
"svtup"
@@ -143,6 +151,7 @@
143151
"stup4"
144152
"sdwrap"
145153
"siwrap"
154+
"vdtr"
146155
"svtup"
147156
"satd"
148157
"%%"
@@ -156,3 +165,10 @@
156165
"!"
157166
"{"
158167
"}"
168+
"vA"
169+
"vB"
170+
"vC"
171+
"vD"
172+
"vS"
173+
"vM"
174+
"vE"

Diff for: fuzz/fuzz_dictionary2.txt

+8
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@
7272
"svopt4"
7373
"config"
7474
"nflag2"
75+
"vdtr"
7576
"--"
7677
"fuzzer"
7778
"t-"
@@ -82,3 +83,10 @@
8283
"dexists"
8384
"fexists"
8485
"fnexists"
86+
"vA"
87+
"vB"
88+
"vC"
89+
"vD"
90+
"vS"
91+
"vM"
92+
"vE"

Diff for: include/CLI/Config.hpp

+4-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ namespace CLI {
2424
// [CLI11:config_hpp:verbatim]
2525
namespace detail {
2626

27-
std::string convert_arg_for_ini(const std::string &arg, char stringQuote = '"', char characterQuote = '\'');
27+
std::string convert_arg_for_ini(const std::string &arg,
28+
char stringQuote = '"',
29+
char characterQuote = '\'',
30+
bool disable_multi_line = false);
2831

2932
/// Comma separated join, adds quotes if needed
3033
std::string ini_join(const std::vector<std::string> &args,

Diff for: include/CLI/ConfigFwd.hpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <algorithm>
1111
#include <fstream>
1212
#include <iostream>
13+
#include <memory>
1314
#include <string>
1415
#include <vector>
1516
// [CLI11:public_includes:end]
@@ -29,7 +30,6 @@ struct ConfigItem {
2930

3031
/// This is the name
3132
std::string name{};
32-
3333
/// Listing of inputs
3434
std::vector<std::string> inputs{};
3535

Diff for: include/CLI/Error.hpp

+3
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,9 @@ class BadNameString : public ConstructionError {
127127
return BadNameString("Long names strings require 2 dashes " + name);
128128
}
129129
static BadNameString BadLongName(std::string name) { return BadNameString("Bad long name: " + name); }
130+
static BadNameString BadPositionalName(std::string name) {
131+
return BadNameString("Invalid positional Name: " + name);
132+
}
130133
static BadNameString DashesOnly(std::string name) {
131134
return BadNameString("Must have a name, not just dashes: " + name);
132135
}

Diff for: include/CLI/Option.hpp

+6-6
Original file line numberDiff line numberDiff line change
@@ -550,12 +550,12 @@ class Option : public OptionBase<Option> {
550550
if(!lnames_.empty()) {
551551
return lnames_[0];
552552
}
553-
if(!pname_.empty()) {
554-
return pname_;
555-
}
556553
if(!snames_.empty()) {
557554
return snames_[0];
558555
}
556+
if(!pname_.empty()) {
557+
return pname_;
558+
}
559559
return envname_;
560560
}
561561
/// The number of times the option expects to be included
@@ -578,13 +578,13 @@ class Option : public OptionBase<Option> {
578578
CLI11_NODISCARD int get_items_expected() const { return get_items_expected_min(); }
579579

580580
/// True if the argument can be given directly
581-
CLI11_NODISCARD bool get_positional() const { return pname_.length() > 0; }
581+
CLI11_NODISCARD bool get_positional() const { return !pname_.empty(); }
582582

583583
/// True if option has at least one non-positional name
584-
CLI11_NODISCARD bool nonpositional() const { return (snames_.size() + lnames_.size()) > 0; }
584+
CLI11_NODISCARD bool nonpositional() const { return (!lnames_.empty() || !snames_.empty()); }
585585

586586
/// True if option has description
587-
CLI11_NODISCARD bool has_description() const { return description_.length() > 0; }
587+
CLI11_NODISCARD bool has_description() const { return !description_.empty(); }
588588

589589
/// Get the description
590590
CLI11_NODISCARD const std::string &get_description() const { return description_; }

Diff for: include/CLI/StringTools.hpp

+32-8
Original file line numberDiff line numberDiff line change
@@ -140,14 +140,16 @@ CLI11_INLINE std::ostream &format_aliases(std::ostream &out, const std::vector<s
140140

141141
/// Verify the first character of an option
142142
/// - is a trigger character, ! has special meaning and new lines would just be annoying to deal with
143-
template <typename T> bool valid_first_char(T c) { return ((c != '-') && (c != '!') && (c != ' ') && c != '\n'); }
143+
template <typename T> bool valid_first_char(T c) {
144+
return ((c != '-') && (static_cast<unsigned char>(c) > 33)); // space and '!' not allowed
145+
}
144146

145147
/// Verify following characters of an option
146148
template <typename T> bool valid_later_char(T c) {
147149
// = and : are value separators, { has special meaning for option defaults,
148-
// and \n would just be annoying to deal with in many places allowing space here has too much potential for
149-
// inadvertent entry errors and bugs
150-
return ((c != '=') && (c != ':') && (c != '{') && (c != ' ') && c != '\n');
150+
// and control codes other than tab would just be annoying to deal with in many places allowing space here has too
151+
// much potential for inadvertent entry errors and bugs
152+
return ((c != '=') && (c != ':') && (c != '{') && ((static_cast<unsigned char>(c) > 32) || c == '\t'));
151153
}
152154

153155
/// Verify an option/subcommand name
@@ -211,17 +213,39 @@ template <typename Callable> inline std::string find_and_modify(std::string str,
211213
}
212214

213215
/// Split a string '"one two" "three"' into 'one two', 'three'
214-
/// Quote characters can be ` ' or "
215-
CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter = '\0');
216+
/// Quote characters can be ` ' or " or bracket characters [{(< with matching to the matching bracket
217+
CLI11_INLINE std::vector<std::string> split_up(std::string str, char delimiter = '\0', bool removeQuotes = true);
218+
219+
/// get the value of an environmental variable or empty string if empty
220+
CLI11_INLINE std::string get_environment_value(const std::string &env_name);
216221

217222
/// This function detects an equal or colon followed by an escaped quote after an argument
218223
/// then modifies the string to replace the equality with a space. This is needed
219224
/// to allow the split up function to work properly and is intended to be used with the find_and_modify function
220225
/// the return value is the offset+1 which is required by the find_and_modify function.
221226
CLI11_INLINE std::size_t escape_detect(std::string &str, std::size_t offset);
222227

223-
/// get the value of an environmental variable or empty string if empty
224-
CLI11_INLINE std::string get_environment_value(const std::string &env_name);
228+
/// @brief detect if a string has escapable characters
229+
/// @param str the string to do the detection on
230+
/// @return true if the string has escapable characters
231+
CLI11_INLINE bool has_escapable_character(const std::string &str);
232+
233+
/// @brief escape all escapable characters
234+
/// @param str the string to escape
235+
/// @return a string with the escapble characters escaped with '\'
236+
CLI11_INLINE std::string add_escaped_characters(const std::string &str);
237+
238+
/// @brief replace the escaped characters with their equivalent
239+
CLI11_INLINE std::string remove_escaped_characters(const std::string &str);
240+
241+
/// generate a string with all non printable characters escaped to hex codes
242+
CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escape);
243+
244+
CLI11_INLINE bool is_binary_escaped_string(const std::string &escaped_string);
245+
246+
/// extract an escaped binary_string
247+
CLI11_INLINE std::string extract_binary_string(const std::string &escaped_string);
248+
225249
} // namespace detail
226250

227251
// [CLI11:string_tools_hpp:end]

0 commit comments

Comments
 (0)