diff --git a/.github/workflows/cmake.yml b/.github/workflows/cmake.yml index a2a9063..2967916 100644 --- a/.github/workflows/cmake.yml +++ b/.github/workflows/cmake.yml @@ -20,11 +20,9 @@ jobs: build-ubuntu: strategy: matrix: - os: [ ubuntu-18.04, ubuntu-20.04, ubuntu-22.04 ] + os: [ ubuntu-20.04, ubuntu-22.04 ] compiler: [ g++-9, g++-10, clang++ ] - include: - - os: ubuntu-18.04 - compiler: g++-7 + name: Build and Test on Ubuntu runs-on: ${{matrix.os}} steps: diff --git a/BUILD.bazel b/BUILD.bazel index a66fae7..a6cd021 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -6,3 +6,11 @@ cc_library( strip_include_prefix = "include", visibility = ["//visibility:public"], ) + +load("@rules_fuzzing//fuzzing:cc_defs.bzl", "cc_fuzz_test") + +cc_fuzz_test( + name = "cxxopts_fuzz_test", + srcs = ["test/fuzz.cpp"], + deps = [":cxxopts"], +) \ No newline at end of file diff --git a/WORKSPACE b/WORKSPACE index e69de29..a14dfb1 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -0,0 +1,16 @@ +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "rules_fuzzing", + sha256 = "23bb074064c6f488d12044934ab1b0631e8e6898d5cf2f6bde087adb01111573", + strip_prefix = "rules_fuzzing-0.3.1", + urls = ["https://github.com/bazelbuild/rules_fuzzing/archive/v0.3.1.zip"], +) + +load("@rules_fuzzing//fuzzing:repositories.bzl", "rules_fuzzing_dependencies") + +rules_fuzzing_dependencies() + +load("@rules_fuzzing//fuzzing:init.bzl", "rules_fuzzing_init") + +rules_fuzzing_init() \ No newline at end of file diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index d3467f3..db09bca 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -51,3 +51,10 @@ add_test(add-subdirectory-test ${CMAKE_CTEST_COMMAND} add_executable(link_test link_a.cpp link_b.cpp) target_link_libraries(link_test cxxopts) + +if(("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") AND ("${CMAKE_SYSTEM}" MATCHES "Linux")) + add_executable(fuzzer fuzz.cpp) + target_link_libraries(fuzzer PRIVATE cxxopts) + target_compile_options(fuzzer PRIVATE -fsanitize=fuzzer) + target_link_options(fuzzer PRIVATE -fsanitize=fuzzer) +endif() diff --git a/test/fuzz.cpp b/test/fuzz.cpp new file mode 100644 index 0000000..241861a --- /dev/null +++ b/test/fuzz.cpp @@ -0,0 +1,107 @@ +#include +#include +#include + +constexpr int kMaxOptions = 1024; +constexpr int kMaxArgSize = 1024; + +enum class ParseableTypes +{ + kInt, + kString, + kVectorString, + kFloat, + kDouble, + + // Marker for fuzzer. + kMaxValue, +}; + +template +void +add_fuzzed_option(cxxopts::Options* options, FuzzedDataProvider* provider) +{ + assert(options); + assert(provider); + + options->add_options()(provider->ConsumeRandomLengthString(kMaxArgSize), + provider->ConsumeRandomLengthString(kMaxArgSize), + cxxopts::value()); +} + +extern "C" int +LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + try + { + FuzzedDataProvider provider(data, size); + + // Randomly generate a usage string. + cxxopts::Options options(provider.ConsumeRandomLengthString(kMaxArgSize), + provider.ConsumeRandomLengthString(kMaxArgSize)); + + // Randomly generate a set of flags configurations. + for (int i = 0; i < provider.ConsumeIntegralInRange(0, kMaxOptions); + i++) + { + switch (provider.ConsumeEnum()) + { + case ParseableTypes::kInt: + add_fuzzed_option(&options, &provider); + break; + case ParseableTypes::kString: + add_fuzzed_option(&options, &provider); + break; + case ParseableTypes::kVectorString: + add_fuzzed_option>(&options, &provider); + break; + case ParseableTypes::kFloat: + add_fuzzed_option(&options, &provider); + break; + case ParseableTypes::kDouble: + add_fuzzed_option(&options, &provider); + break; + default: + break; + } + } + // Sometimes allow unrecognised options. + if (provider.ConsumeBool()) + { + options.allow_unrecognised_options(); + } + // Sometimes allow trailing positional arguments. + if (provider.ConsumeBool()) + { + std::string positional_option_name = + provider.ConsumeRandomLengthString(kMaxArgSize); + options.add_options()(positional_option_name, + provider.ConsumeRandomLengthString(kMaxArgSize), + cxxopts::value>()); + options.parse_positional({positional_option_name}); + } + + // Build command line input. + const int argc = provider.ConsumeIntegralInRange(1, kMaxOptions); + + std::vector command_line_container; + command_line_container.reserve(argc); + + std::vector argv; + argv.reserve(argc); + + for (int i = 0; i < argc; i++) + { + command_line_container.push_back( + provider.ConsumeRandomLengthString(kMaxArgSize)); + argv.push_back(command_line_container[i].c_str()); + } + + // Parse command line; + auto result = options.parse(argc, argv.data()); + } catch (...) + { + } + + return 0; +}