diff --git a/fuzz/corpus/noexcept_mode_fuzzer/bad-float.seed b/fuzz/corpus/noexcept_mode_fuzzer/bad-float.seed new file mode 100644 index 0000000..cf83e12 --- /dev/null +++ b/fuzz/corpus/noexcept_mode_fuzzer/bad-float.seed @@ -0,0 +1 @@ +--foo=7.5 \ No newline at end of file diff --git a/fuzz/corpus/noexcept_mode_fuzzer/completion.seed b/fuzz/corpus/noexcept_mode_fuzzer/completion.seed new file mode 100644 index 0000000..1dc3c5f --- /dev/null +++ b/fuzz/corpus/noexcept_mode_fuzzer/completion.seed @@ -0,0 +1 @@ +--completion bash 1 prog - \ No newline at end of file diff --git a/fuzz/corpus/noexcept_mode_fuzzer/extra.seed b/fuzz/corpus/noexcept_mode_fuzzer/extra.seed new file mode 100644 index 0000000..9a2b1a6 --- /dev/null +++ b/fuzz/corpus/noexcept_mode_fuzzer/extra.seed @@ -0,0 +1 @@ +--bar -b \ No newline at end of file diff --git a/fuzz/corpus/noexcept_mode_fuzzer/good-parse.seed b/fuzz/corpus/noexcept_mode_fuzzer/good-parse.seed new file mode 100644 index 0000000..2faaaab --- /dev/null +++ b/fuzz/corpus/noexcept_mode_fuzzer/good-parse.seed @@ -0,0 +1 @@ +--foo 85 --req ok cmd \ No newline at end of file diff --git a/fuzz/corpus/noexcept_mode_fuzzer/help.seed b/fuzz/corpus/noexcept_mode_fuzzer/help.seed new file mode 100644 index 0000000..805b5bb --- /dev/null +++ b/fuzz/corpus/noexcept_mode_fuzzer/help.seed @@ -0,0 +1 @@ +-h \ No newline at end of file diff --git a/fuzz/corpus/noexcept_mode_fuzzer/map-error.seed b/fuzz/corpus/noexcept_mode_fuzzer/map-error.seed new file mode 100644 index 0000000..8bf0a05 --- /dev/null +++ b/fuzz/corpus/noexcept_mode_fuzzer/map-error.seed @@ -0,0 +1 @@ +--mf yellow \ No newline at end of file diff --git a/fuzz/corpus/noexcept_mode_fuzzer/success-reparse.seed b/fuzz/corpus/noexcept_mode_fuzzer/success-reparse.seed new file mode 100644 index 0000000..dc9a0e6 --- /dev/null +++ b/fuzz/corpus/noexcept_mode_fuzzer/success-reparse.seed @@ -0,0 +1 @@ +--req ok cmd -n 3 --bar \ No newline at end of file diff --git a/fuzz/corpus/noexcept_mode_fuzzer/validation.seed b/fuzz/corpus/noexcept_mode_fuzzer/validation.seed new file mode 100644 index 0000000..ce433af --- /dev/null +++ b/fuzz/corpus/noexcept_mode_fuzzer/validation.seed @@ -0,0 +1 @@ +-xz \ No newline at end of file diff --git a/fuzz/noexcept_mode_fuzzer.cxx b/fuzz/noexcept_mode_fuzzer.cxx new file mode 100644 index 0000000..4a022eb --- /dev/null +++ b/fuzz/noexcept_mode_fuzzer.cxx @@ -0,0 +1,150 @@ +/* + * ARGS_NOEXCEPT compile-variant harness for args.hxx. + * + * Defining ARGS_NOEXCEPT before including args.hxx selects a different + * compile-time configuration of the library: instead of throwing, the + * parser records an error status that callers read back with GetError() + * and GetErrorMsg(). This selects alternative code paths throughout the + * whole library (parser, readers, completion, subparsers), so it is built + * as a separate binary rather than fed through the same targets. + * + * The harness mirrors the contract of test/noexcept_mode.cxx and + * test/noexcept_completion.cxx: help, parse, validation, map, and extra + * errors, mapped flags, required options, a validated group, commands, + * completion requests, and a failure-to-success re-parse to exercise the + * error-status reset between parses. + * + * NOTE: this file deliberately defines ARGS_NOEXCEPT itself and uses the + * error-status API, so it MUST NOT be compiled without the macro and is + * intentionally named *.cxx to stay outside the *_fuzzer.cpp discovery + * glob; the oss-fuzz build script compiles it explicitly. + */ + +#define ARGS_NOEXCEPT + +#include +#include +#include +#include +#include + +#include + +#include + +namespace +{ + +enum class Mapping +{ + def, + foo, + bar +}; + +const std::unordered_map kMap{ + {"default", Mapping::def}, {"foo", Mapping::foo}, {"bar", Mapping::bar}}; + +const char *const kTokens[] = { + "-h", "--Help", "--bar", "-b", "--bar=test", "--bar", "-b", + "--foo=7.5", "--foo", "85", "7a", "7e4", "--foo=", + "--mf", "yellow", "YeLLoW", "--mf=default", + "-x", "-xz", "-y", "-y", "-x", + "--req", "value", "cmd", "-n", "3", + "--completion", "bash", "1", "prog", "-", "--map=", + "--", "extra", "1", "", "-z", "garbage", +}; + +template +constexpr size_t ArraySize(T (&)[N]) +{ + return N; +} + +std::vector BuildArgv(FuzzedDataProvider &fdp) +{ + const size_t tokenCount = fdp.ConsumeIntegralInRange(0, 10); + std::vector argv; + argv.reserve(tokenCount); + for (size_t i = 0; i < tokenCount; ++i) + { + if (fdp.ConsumeBool()) + { + argv.emplace_back(kTokens[fdp.ConsumeIntegralInRange(0, ArraySize(kTokens) - 1)]); + } + else + { + argv.emplace_back(fdp.ConsumeRandomLengthString(16)); + } + } + return argv; +} + +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + FuzzedDataProvider fdp(data, size); + + args::ArgumentParser parser("noexcept_mode_fuzzer", "epilog"); + args::HelpFlag help(parser, "help", "help", {'h', "help"}); + args::Flag bar(parser, "bar", "bar", {'b', "bar"}, true); + args::ValueFlag foo(parser, "foo", "foo", {'f', "foo"}); + args::Group nand(parser, "at-most-one group", args::Group::Validators::AtMostOne); + args::Flag x(nand, "x", "x", {'x'}); + args::Flag y(nand, "y", "y", {'y'}); + args::MapFlag mf(parser, "mf", "mf", {"mf"}, kMap); + args::ValueFlag required(parser, "req", "req", {"req"}, args::Options::Required); + args::CompletionFlag completion(parser, {"completion"}); + + args::Command cmd(parser, "cmd", "subcommand", [](args::Subparser &sp) + { + args::ValueFlag num(sp, "num", "num", {'n', "num"}); + sp.Parse(); + if (num) + { + (void)*num; + } + }); + + parser.RequireCommand(fdp.ConsumeBool()); + + // First parse: arbitrary token mix; read back the error status. + parser.ParseArgs(BuildArgv(fdp)); + const args::Error first = parser.GetError(); + (void)parser.GetErrorMsg(); + + // Consume results in the non-throwing configuration. + if (bar) + { + (void)bar.Get(); + } + if (foo) + { + (void)*foo; + } + if (mf) + { + (void)*mf; + } + if (required) + { + (void)*required; + } + (void)args::get(completion); + + // Failure-to-success (and success-to-failure) re-parse: exercises the + // error-status reset carried by ParseArgs between parses. + if (first == args::Error::None) + { + parser.ParseArgs(std::vector{"--foo=abc"}); + } + else + { + parser.ParseArgs(std::vector{"--req", "ok", "cmd"}); + } + (void)parser.GetError(); + (void)parser.GetErrorMsg(); + + return 0; +}