Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add a round trip test to the fuzzer #1060

Merged
merged 23 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7795d8a
add a round trip test to the fuzzer and fix a bug in the string escap…
phlptp Jul 27, 2024
58c0dd9
style: pre-commit.ci fixes
pre-commit-ci[bot] Jul 27, 2024
1f03780
update fuzz fail test
phlptp Jul 27, 2024
6e7959a
fix fuzzer app
phlptp Jul 27, 2024
ab17a94
style: pre-commit.ci fixes
pre-commit-ci[bot] Jul 27, 2024
4136f54
make sure "++" is not a valid name string.
phlptp Jul 28, 2024
ba5bcf8
style: pre-commit.ci fixes
pre-commit-ci[bot] Jul 28, 2024
efe1477
add trap for singular empty vector as a string argument
phlptp Jul 28, 2024
d1dc447
style: pre-commit.ci fixes
pre-commit-ci[bot] Jul 28, 2024
e9db32c
Merge remote-tracking branch 'remotes/upstream/main' into fuzz_roundtrip
phlptp Jul 29, 2024
f2113a5
fix inconsistency with storing results with default values and force_…
phlptp Jul 29, 2024
ec9247a
style: pre-commit.ci fixes
pre-commit-ci[bot] Jul 29, 2024
b359bc1
fix handling of 1 line multi-line quoted strings
phlptp Jul 29, 2024
5e9a26a
style: pre-commit.ci fixes
pre-commit-ci[bot] Jul 29, 2024
61293c9
add code coverage test
phlptp Jul 29, 2024
bb45caa
add more coverage
phlptp Jul 29, 2024
e668094
style: pre-commit.ci fixes
pre-commit-ci[bot] Jul 29, 2024
f12fdd4
add fuzz test
phlptp Sep 4, 2024
a435b9c
clean up handling of {} for empty vectors in config files
phlptp Sep 20, 2024
21484b6
style: pre-commit.ci fixes
pre-commit-ci[bot] Sep 20, 2024
380429d
remove unneeded code
phlptp Sep 20, 2024
2be38c5
add config test for coverage
phlptp Sep 21, 2024
83694dc
style: pre-commit.ci fixes
pre-commit-ci[bot] Sep 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,9 @@ string, with the dash or dashes. An option or flag can have as many names as you
want, and afterward, using `count`, you can use any of the names, with dashes as
needed, to count the options. One of the names is allowed to be given without
proceeding dash(es); if present the option is a positional option, and that name
will be used on the help line for its positional form.
will be used on the help line for its positional form. The string `++` is also
not allowed as option name due to its use as an array separator and marker on
config files.

The `add_option_function<type>(...` function will typically require the template
parameter be given unless a `std::function` object with an exact match is
Expand Down
2 changes: 1 addition & 1 deletion fuzz/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ if(CMAKE_CXX_STANDARD GREATER 16)
COMMAND
cli11_app_fuzzer corp -max_total_time=${CLI11_FUZZ_TIME_APP} -max_len=2148
-dict=${CMAKE_CURRENT_SOURCE_DIR}/fuzz_dictionary1.txt
-exact_artifact_path=${CLI11_FUZZ_ARTIFACT_PATH}/cli11_app_fail_artifact.txt)
-exact_artifact_path=${CLI11_FUZZ_ARTIFACT_PATH}/cli11_app_roundtrip_fail_artifact.txt)

add_custom_target(
QUICK_CLI11_FILE_FUZZ
Expand Down
10 changes: 9 additions & 1 deletion fuzz/cli11_app_fuzz.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,17 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
parseString.erase(0, 25);
}
CLI::FuzzApp fuzzdata;
CLI::FuzzApp fuzzdata2;
auto app = fuzzdata.generateApp();
auto app2 = fuzzdata2.generateApp();
try {
if(!optionString.empty()) {
app->add_option(optionString, fuzzdata.buffer);
app2->add_option(optionString, fuzzdata2.buffer);
}
if(!flagString.empty()) {
app->add_flag(flagString, fuzzdata.intbuffer);
app2->add_flag(flagString, fuzzdata2.intbuffer);
}
} catch(const CLI::ConstructionError &e) {
return 0; // Non-zero return values are reserved for future use.
Expand All @@ -50,6 +54,10 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) {
std::string configOut = app->config_to_str();
app->clear();
std::stringstream out(configOut);
app->parse_from_stream(out);
app2->parse_from_stream(out);
auto result = fuzzdata2.compare(fuzzdata);
if(!result) {
throw CLI::ValidationError("fuzzer", "file input results don't match parse results");
}
return 0;
}
144 changes: 144 additions & 0 deletions fuzz/fuzzApp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// SPDX-License-Identifier: BSD-3-Clause

#include "fuzzApp.hpp"
#include <algorithm>

namespace CLI {
/*
Expand Down Expand Up @@ -148,4 +149,147 @@ std::shared_ptr<CLI::App> FuzzApp::generateApp() {
return fApp;
}

bool FuzzApp::compare(const FuzzApp &other) const {
if(val32 != other.val32) {
return false;
}
if(val16 != other.val16) {
return false;
}
if(val8 != other.val8) {
return false;
}
if(val64 != other.val64) {
return false;
}

if(uval32 != other.uval32) {
return false;
}
if(uval16 != other.uval16) {
return false;
}
if(uval8 != other.uval8) {
return false;
}
if(uval64 != other.uval64) {
return false;
}

if(atomicval64 != other.atomicval64) {
return false;
}
if(atomicuval64 != other.atomicuval64) {
return false;
}

if(v1 != other.v1) {
return false;
}
if(v2 != other.v2) {
return false;
}

if(vv1 != other.vv1) {
return false;
}
if(vstr != other.vstr) {
return false;
}

if(vecvecd != other.vecvecd) {
return false;
}
if(vvs != other.vvs) {
return false;
}
if(od1 != other.od1) {
return false;
}
if(ods != other.ods) {
return false;
}
if(p1 != other.p1) {
return false;
}
if(p2 != other.p2) {
return false;
}
if(t1 != other.t1) {
return false;
}
if(tcomplex != other.tcomplex) {
return false;
}
if(tcomplex2 != other.tcomplex2) {
return false;
}
if(vectup != other.vectup) {
return false;
}
if(vstrv != other.vstrv) {
return false;
}

if(flag1 != other.flag1) {
return false;
}
if(flagCnt != other.flagCnt) {
return false;
}
if(flagAtomic != other.flagAtomic) {
return false;
}

if(iwrap.value() != other.iwrap.value()) {
return false;
}
if(dwrap.value() != other.dwrap.value()) {
return false;
}
if(swrap.value() != other.swrap.value()) {
return false;
}
if(buffer != other.buffer) {
return false;
}
if(intbuffer != other.intbuffer) {
return false;
}
if(doubleAtomic != other.doubleAtomic) {
return false;
}

// for testing restrictions and reduction methods
if(vstrA != other.vstrA) {
return false;
}
if(vstrB != other.vstrB) {
return false;
}
if(vstrC != other.vstrC) {
return false;
}
if(vstrD != other.vstrD) {
// the return result if reversed so it can alternate
std::vector<std::string> res = vstrD;
std::reverse(res.begin(), res.end());
if(res != other.vstrD) {
return false;
}
}
if(vstrE != other.vstrE) {
return false;
}
if(vstrF != other.vstrF) {
return false;
}
if(mergeBuffer != other.mergeBuffer) {
return false;
}
if(validator_strings != other.validator_strings) {
return false;
}
return true;
}
} // namespace CLI
4 changes: 3 additions & 1 deletion fuzz/fuzzApp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,10 @@ class stringWrapper {
class FuzzApp {
public:
FuzzApp() = default;

/** generate a fuzzing application with a bunch of different interfaces*/
std::shared_ptr<CLI::App> generateApp();
/** compare two fuzz apps for equality*/
CLI11_NODISCARD bool compare(const FuzzApp &other) const;

int32_t val32{0};
int16_t val16{0};
Expand Down
4 changes: 2 additions & 2 deletions include/CLI/Error.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,8 @@ class BadNameString : public ConstructionError {
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);
static BadNameString ReservedName(std::string name) {
return BadNameString("Names '-','--','++' are reserved and not allowed as option names " + name);
}
static BadNameString MultiPositionalNames(std::string name) {
return BadNameString("Only one positional name allowed, remove: " + name);
Expand Down
7 changes: 6 additions & 1 deletion include/CLI/StringTools.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,12 @@ template <typename T> std::string join(const T &v, std::string delim = ",") {
while(beg != end) {
s << delim << *beg++;
}
return s.str();
auto rval = s.str();
if(!rval.empty() && delim.size() == 1 && rval.back() == delim[0]) {
// remove trailing delimiter if the last entry was empty
rval.pop_back();
}
return rval;
}

/// Simple function to join a string from processed elements
Expand Down
6 changes: 4 additions & 2 deletions include/CLI/impl/App_inl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -1547,7 +1547,9 @@ CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t

if(!converted) {
errno = 0;
res = op->get_flag_value(item.name, res);
if(res != "{}" || op->get_expected_max() <= 1) {
res = op->get_flag_value(item.name, res);
}
}

op->add_result(res);
Expand Down Expand Up @@ -1896,7 +1898,7 @@ App::_parse_arg(std::vector<std::string> &args, detail::Classifier current_type,
// using dot notation is equivalent to single argument subcommand
auto *sub = _find_subcommand(arg_name.substr(0, dotloc), true, false);
if(sub != nullptr) {
auto v = args.back();
std::string v = args.back();
args.pop_back();
arg_name = arg_name.substr(dotloc + 1);
if(arg_name.size() > 1) {
Expand Down
13 changes: 13 additions & 0 deletions include/CLI/impl/Config_inl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,19 @@ inline std::vector<ConfigItem> ConfigBase::from_config(std::istream &input) cons
if(!item.empty() && item.back() == '\\') {
item.pop_back();
lineExtension = true;
} else if(detail::hasMLString(item, keyChar)) {
// deal with the first line closing the multiline literal
item.pop_back();
item.pop_back();
item.pop_back();
if(keyChar == '\"') {
try {
item = detail::remove_escaped_characters(item);
} catch(const std::invalid_argument &iarg) {
throw CLI::ParseError(iarg.what(), CLI::ExitCodes::InvalidError);
}
}
inMLineValue = false;
}
while(inMLineValue) {
std::string l2;
Expand Down
18 changes: 11 additions & 7 deletions include/CLI/impl/Option_inl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -284,7 +284,9 @@ CLI11_NODISCARD CLI11_INLINE std::string Option::get_name(bool positional, bool
}

CLI11_INLINE void Option::run_callback() {
bool used_default_str = false;
if(force_callback_ && results_.empty()) {
used_default_str = true;
add_result(default_str_);
}
if(current_option_state_ == option_state::parsing) {
Expand All @@ -294,16 +296,18 @@ CLI11_INLINE void Option::run_callback() {

if(current_option_state_ < option_state::reduced) {
_reduce_results(proc_results_, results_);
current_option_state_ = option_state::reduced;
}
if(current_option_state_ >= option_state::reduced) {
current_option_state_ = option_state::callback_run;
if(!(callback_)) {
return;
}

current_option_state_ = option_state::callback_run;
if(callback_) {
const results_t &send_results = proc_results_.empty() ? results_ : proc_results_;
bool local_result = callback_(send_results);

if(used_default_str) {
// we only clear the results if the callback was actually used
// otherwise the callback is the storage of the default
results_.clear();
proc_results_.clear();
}
if(!local_result)
throw ConversionError(get_name(), results_);
}
Expand Down
4 changes: 2 additions & 2 deletions include/CLI/impl/Split_inl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ get_names(const std::vector<std::string> &input) {
long_names.push_back(name);
else
throw BadNameString::BadLongName(name);
} else if(name == "-" || name == "--") {
throw BadNameString::DashesOnly(name);
} else if(name == "-" || name == "--" || name == "++") {
throw BadNameString::ReservedName(name);
} else {
if(!pos_name.empty())
throw BadNameString::MultiPositionalNames(name);
Expand Down
3 changes: 2 additions & 1 deletion include/CLI/impl/StringTools_inl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -459,7 +459,8 @@ CLI11_INLINE std::string binary_escape_string(const std::string &string_to_escap
if(escaped_string != string_to_escape) {
auto sqLoc = escaped_string.find('\'');
while(sqLoc != std::string::npos) {
escaped_string.replace(sqLoc, sqLoc + 1, "\\x27");
escaped_string[sqLoc] = '\\';
escaped_string.insert(sqLoc + 1, "x27");
sqLoc = escaped_string.find('\'');
}
escaped_string.insert(0, "'B\"(");
Expand Down
12 changes: 12 additions & 0 deletions tests/AppTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2072,6 +2072,18 @@ TEST_CASE_METHOD(TApp, "EnvOnly", "[app]") {
CHECK_THROWS_AS(run(), CLI::RequiredError);
}

// reported bug #1013 on github
TEST_CASE_METHOD(TApp, "groupEnvRequired", "[app]") {
std::string str;
auto *group1 = app.add_option_group("group1");
put_env("CLI11_TEST_GROUP_REQUIRED", "string_abc");
group1->add_option("-f", str, "f")->envname("CLI11_TEST_GROUP_REQUIRED")->required();

run();
CHECK(str == "string_abc");
unset_env("CLI11_TEST_GROUP_REQUIRED");
}

TEST_CASE_METHOD(TApp, "RangeInt", "[app]") {
int x{0};
app.add_option("--one", x)->check(CLI::Range(3, 6));
Expand Down
Loading
Loading