diff --git a/.codecov.yml b/.codecov.yml index 2664e71a..61c2e2f2 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -4,3 +4,4 @@ ignore: - "book" - "docs" - "test_package" + - "fuzz" diff --git a/.github/codecov.yml b/.github/codecov.yml index a0b06674..0e106f71 100644 --- a/.github/codecov.yml +++ b/.github/codecov.yml @@ -1,6 +1,6 @@ codecov: notify: - after_n_builds: 4 + after_n_builds: 8 coverage: status: project: diff --git a/.github/workflows/fuzz.yml b/.github/workflows/fuzz.yml new file mode 100644 index 00000000..75d161b3 --- /dev/null +++ b/.github/workflows/fuzz.yml @@ -0,0 +1,54 @@ +name: Fuzz +on: + workflow_dispatch: + push: + branches: + - main + - v* + pull_request: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + quick_fuzz1: + name: quickfuzz1 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - name: Configure + run: | + cmake -S . -B build \ + -DCMAKE_CXX_STANDARD=17 \ + -DCLI11_SINGLE_FILE_TESTS=OFF \ + -DCLI11_BUILD_EXAMPLES=OFF \ + -DCLI11_FUZZ_TARGET=ON \ + -DCLI11_BUILD_TESTS=OFF \ + -DCLI11_BUILD_DOCS=OFF \ + -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_COMPILER_FORCED=ON \ + -DCMAKE_CXX_FLAGS="-g -O1 -fsanitize=fuzzer,undefined,address" + + - name: Build + run: cmake --build build -j4 + + - name: Test + run: | + cd build + make QUICK_CLI11_APP_FUZZ + + - name: Test2 + run: | + cd build + make QUICK_CLI11_FILE_FUZZ + + + - name: artifacts + if: failure() + uses: actions/upload-artifact@v3 + with: + name: file_failure + path: ./build/fuzz/cli11_*_fail_artifact.txt diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 13ae3393..ab255fe9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -48,7 +48,7 @@ jobs: - name: Prepare coverage run: | lcov --directory . --capture --output-file coverage.info - lcov --remove coverage.info '*/tests/*' '*/examples/*' '/usr/*' '*/book/*' --output-file coverage.info + lcov --remove coverage.info '*/tests/*' '*/examples/*' '/usr/*' '*/book/*' '*/fuzz/*' --output-file coverage.info lcov --list coverage.info working-directory: build diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 59850d64..6f81b963 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,4 @@ +exclude: ^(.github/workflows/|docs/img/) ci: autoupdate_commit_msg: "chore(deps): pre-commit.ci autoupdate" autofix_commit_msg: "style: pre-commit.ci fixes" diff --git a/CMakeLists.txt b/CMakeLists.txt index 34733612..c9f5ffa2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -132,6 +132,9 @@ endif() include(CLI11Warnings) +# build the fuzzing example or fuzz entry point +add_subdirectory(fuzz) + add_subdirectory(src) # Allow tests to be run on CUDA diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 24576eb4..1bb4d077 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -28,7 +28,6 @@ jobs: - bash: cpplint --counting=detailed --recursive examples include/CLI tests displayName: Checking against google style guide - # TODO: Fix macOS error and windows warning in c++17 mode - job: Native strategy: matrix: @@ -38,13 +37,13 @@ jobs: vmImage: "ubuntu-latest" cli11.precompile: ON macOS17: - vmImage: "macOS-latest" + vmImage: "macOS-12" cli11.std: 17 macOS11: - vmImage: "macOS-latest" + vmImage: "macOS-11" cli11.std: 11 macOS11PC: - vmImage: "macOS-latest" + vmImage: "macOS-11" cli11.std: 11 cli11.precompile: ON Windows17: diff --git a/fuzz/CMakeLists.txt b/fuzz/CMakeLists.txt new file mode 100644 index 00000000..21df4028 --- /dev/null +++ b/fuzz/CMakeLists.txt @@ -0,0 +1,48 @@ +# Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +# under NSF AWARD 1414736 and by the respective contributors. +# All rights reserved. +# +# SPDX-License-Identifier: BSD-3-Clause + +if(CMAKE_CXX_STANDARD GREATER 16) + if(CLI11_FUZZ_TARGET) + + add_executable(cli11_app_fuzzer cli11_app_fuzz.cpp fuzzApp.cpp fuzzApp.hpp) + target_link_libraries(cli11_app_fuzzer PUBLIC CLI11) + set_property(TARGET cli11_app_fuzzer PROPERTY FOLDER "Tests") + + add_executable(cli11_file_fuzzer cli11_file_fuzz.cpp fuzzApp.cpp fuzzApp.hpp) + target_link_libraries(cli11_file_fuzzer PUBLIC CLI11) + set_property(TARGET cli11_file_fuzzer PROPERTY FOLDER "Tests") + + if(NOT CLI11_FUZZ_ARTIFACT_PATH) + set(CLI11_FUZZ_ARTIFACT_PATH ${PROJECT_BINARY_DIR}/fuzz) + endif() + + if(NOT CLI11_FUZZ_TIME) + set(CLI11_FUZZ_TIME 360) + endif() + add_custom_target( + QUICK_CLI11_APP_FUZZ + COMMAND ${CMAKE_COMMAND} -E make_directory corp + COMMAND + cli11_app_fuzzer corp -max_total_time=${CLI11_FUZZ_TIME} -max_len=2048 + -dict=${CMAKE_CURRENT_SOURCE_DIR}/fuzz_dictionary1.txt + -exact_artifact_path=${CLI11_FUZZ_ARTIFACT_PATH}/cli11_app_fail_artifact.txt) + + add_custom_target( + QUICK_CLI11_FILE_FUZZ + COMMAND ${CMAKE_COMMAND} -E make_directory corp + COMMAND + cli11_file_fuzzer corp -max_total_time=${CLI11_FUZZ_TIME} -max_len=2048 + -dict=${CMAKE_CURRENT_SOURCE_DIR}/fuzz_dictionary2.txt + -exact_artifact_path=${CLI11_FUZZ_ARTIFACT_PATH}/cli11_file_fail_artifact.txt) + + else() + if(CLI11_BUILD_EXAMPLES) + add_executable(cli11Fuzz fuzzCommand.cpp fuzzApp.cpp fuzzApp.hpp) + target_link_libraries(cli11Fuzz PUBLIC CLI11) + set_property(TARGET cli11Fuzz PROPERTY FOLDER "Examples") + endif() + endif() +endif() diff --git a/fuzz/cli11_app_fuzz.cpp b/fuzz/cli11_app_fuzz.cpp new file mode 100644 index 00000000..7cd10b88 --- /dev/null +++ b/fuzz/cli11_app_fuzz.cpp @@ -0,0 +1,30 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "fuzzApp.hpp" +#include +#include +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if(Size == 0) { + return 0; + } + std::string parseString(reinterpret_cast(Data), Size); + + CLI::FuzzApp fuzzdata; + + auto app = fuzzdata.generateApp(); + 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. +} diff --git a/fuzz/cli11_file_fuzz.cpp b/fuzz/cli11_file_fuzz.cpp new file mode 100644 index 00000000..e769114e --- /dev/null +++ b/fuzz/cli11_file_fuzz.cpp @@ -0,0 +1,31 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "fuzzApp.hpp" +#include +#include +#include +#include +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) { + if(Size == 0) { + return 0; + } + std::string parseString(reinterpret_cast(Data), Size); + std::stringstream out(parseString); + CLI::FuzzApp fuzzdata; + + auto app = fuzzdata.generateApp(); + try { + app->parse_from_stream(out); + } 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. +} diff --git a/fuzz/fuzzApp.cpp b/fuzz/fuzzApp.cpp new file mode 100644 index 00000000..dc401f93 --- /dev/null +++ b/fuzz/fuzzApp.cpp @@ -0,0 +1,85 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "fuzzApp.hpp" + +namespace CLI { +/* +int32_t val32{0}; + int16_t val16{0}; + int8_t val8{0}; + int64_t val64{0}; + + uint32_t uval32{0}; + uint16_t uval16{0}; + uint8_t uval8{0}; + uint64_t uval64{0}; + + std::atomic atomicval64{0}; + std::atomic atomicuval64{0}; + + double v1{0}; + float v2{0}; + + std::vector vv1; + std::vector vstr; + std::vector> vecvecd; + std::vector> vvs; + std::optional od1; + std::optional ods; + std::pair p1; + std::pair, std::string> p2; + std::tuple> t1; + std::tuple>,std::string, double>,std::vector, +std::optional> tcomplex; std::string_view vstrv; + + bool flag1{false}; + int flagCnt{0}; + std::atomic flagAtomic{false}; + */ +std::shared_ptr FuzzApp::generateApp() { + auto fApp = std::make_shared("fuzzing App", "fuzzer"); + fApp->set_config("--config"); + fApp->add_flag("-a,--flag"); + fApp->add_flag("-b,--flag2", flag1); + fApp->add_flag("-c{34},--flag3{1}", flagCnt)->disable_flag_override(); + fApp->add_flag("-e,--flagA", flagAtomic); + + fApp->add_option("-d,--opt1", val8); + fApp->add_option("--opt2", val16); + fApp->add_option("--opt3", val32); + fApp->add_option("--opt4", val64); + + fApp->add_option("--opt5", uval8); + fApp->add_option("--opt6", uval16); + fApp->add_option("--opt7", uval32); + fApp->add_option("--opt8", uval64); + + fApp->add_option("--aopt1", atomicval64); + fApp->add_option("--aopt2", atomicuval64); + + fApp->add_option("--dopt1", v1); + fApp->add_option("--dopt2", v2); + + fApp->add_option("--vopt1", vv1); + fApp->add_option("--vopt2", vvs); + fApp->add_option("--vopt3", vstr); + fApp->add_option("--vopt4", vecvecd); + + fApp->add_option("--oopt1", od1); + fApp->add_option("--oopt2", ods); + + fApp->add_option("--tup1", p1); + fApp->add_option("--tup2", t1); + fApp->add_option("--tup4", tcomplex); + + fApp->add_option("--dwrap", dwrap); + fApp->add_option("--iwrap", iwrap); + + return fApp; +} + +} // namespace CLI diff --git a/fuzz/fuzzApp.hpp b/fuzz/fuzzApp.hpp new file mode 100644 index 00000000..01600cc2 --- /dev/null +++ b/fuzz/fuzzApp.hpp @@ -0,0 +1,92 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause +#pragma once + +#ifdef CLI11_SINGLE_FILE +#include "CLI11.hpp" +#else +#include "CLI/CLI.hpp" +#endif + +#include +#include +#include +#include +#include +#include +#include + +namespace CLI { + +class intWrapper64 { + public: + intWrapper64() = default; + explicit intWrapper64(int64_t v) : val(v){}; + CLI11_NODISCARD int64_t value() const { return val; } + + private: + int64_t val{0}; +}; + +class doubleWrapper { + public: + doubleWrapper() = default; + explicit doubleWrapper(double v) : val(v){}; + CLI11_NODISCARD double value() const { return val; } + + private: + double val{0.0}; +}; + +class FuzzApp { + public: + FuzzApp() = default; + + std::shared_ptr generateApp(); + + int32_t val32{0}; + int16_t val16{0}; + int8_t val8{0}; + int64_t val64{0}; + + uint32_t uval32{0}; + uint16_t uval16{0}; + uint8_t uval8{0}; + uint64_t uval64{0}; + + std::atomic atomicval64{0}; + std::atomic atomicuval64{0}; + + double v1{0}; + float v2{0}; + + std::vector vv1{}; + std::vector vstr{}; + std::vector> vecvecd{}; + std::vector> vvs{}; + std::optional od1{}; + std::optional ods{}; + std::pair p1{}; + std::pair, std::string> p2{}; + std::tuple> t1{}; + std::tuple>, std::string, double>, + std::vector, + std::optional> + tcomplex{}; + std::tuple>, std::string, double>, + std::vector, + std::optional> + tcomplex2{}; + std::string_view vstrv = ""; + + bool flag1{false}; + int flagCnt{0}; + std::atomic flagAtomic{false}; + + intWrapper64 iwrap{0}; + doubleWrapper dwrap{0.0}; +}; +} // namespace CLI diff --git a/fuzz/fuzzCommand.cpp b/fuzz/fuzzCommand.cpp new file mode 100644 index 00000000..07ab0df2 --- /dev/null +++ b/fuzz/fuzzCommand.cpp @@ -0,0 +1,24 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "fuzzApp.hpp" +#include +#include + +int main(int argc, char **argv) { + + CLI::FuzzApp fuzzdata; + + auto app = fuzzdata.generateApp(); + try { + + app->parse(argc, argv); + } catch(const CLI::ParseError &e) { + (app)->exit(e); + // this just indicates we caught an error known by CLI + } + return 0; +} diff --git a/fuzz/fuzz_dictionary1.txt b/fuzz/fuzz_dictionary1.txt new file mode 100644 index 00000000..c044eecd --- /dev/null +++ b/fuzz/fuzz_dictionary1.txt @@ -0,0 +1,34 @@ +###### Recommended dictionary. +"-a" +"-b" +"-c" +"-d" +"-e" +"--flag1" +"--flag" +"--flag2" +"--flagA" +"--opt1" +"--opt2" +"--opt3" +"--opt4" +"--opt5" +"--opt6" +"--opt7" +"--opt8" +"--opt9" +"--aopt1" +"--aopt2" +"--dopt1" +"--dopt2" +"--vopt1" +"--vopt2" +"--vopt3" +"--vopt4" +"--oopt1" +"--oopt2" +"--tup1" +"--tup2" +"--tup4" +"--dwrap" +"--iwrap" diff --git a/fuzz/fuzz_dictionary2.txt b/fuzz/fuzz_dictionary2.txt new file mode 100644 index 00000000..12dd8f1f --- /dev/null +++ b/fuzz/fuzz_dictionary2.txt @@ -0,0 +1,37 @@ +###### Recommended dictionary. +"a" +"b" +"c" +"d" +"e" +"flag1" +"flag" +"flag2" +"flagA" +"opt1" +"opt2" +"opt3" +"opt4" +"opt5" +"opt6" +"opt7" +"opt8" +"opt9" +"aopt1" +"aopt2" +"dopt1" +"dopt2" +"vopt1" +"vopt2" +"vopt3" +"vopt4" +"=" +"oopt1" +"oopt2" +"tup1" +"tup2" +"tup4" +"tup2" +"tup4" +"dwrap" +"iwrap" diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index 80104716..9d43ea36 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -1239,9 +1239,17 @@ bool lexical_assign(const std::string &input, AssignTo &output) { output = 0; return true; } - int val = 0; + int val{0}; if(lexical_cast(input, val)) { +#if defined(__clang__) +/* on some older clang compilers */ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wsign-conversion" +#endif output = val; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif return true; } return false; @@ -1296,11 +1304,13 @@ template = detail::dummy> bool lexical_conversion(const std::vector &strings, AssignTo &output) { // the remove const is to handle pair types coming from a container - typename std::remove_const::type>::type v1; - typename std::tuple_element<1, ConvertTo>::type v2; - bool retval = lexical_assign(strings[0], v1); + using FirstType = typename std::remove_const::type>::type; + using SecondType = typename std::tuple_element<1, ConvertTo>::type; + FirstType v1; + SecondType v2; + bool retval = lexical_assign(strings[0], v1); if(strings.size() > 1) { - retval = retval && lexical_assign(strings[1], v2); + retval = retval && lexical_assign(strings[1], v2); } if(retval) { output = AssignTo{v1, v2}; @@ -1460,7 +1470,7 @@ tuple_type_conversion(std::vector &strings, AssignTo &output) { std::size_t index{subtype_count_min::value}; const std::size_t mx_count{subtype_count::value}; - const std::size_t mx{(std::max)(mx_count, strings.size())}; + const std::size_t mx{(std::min)(mx_count, strings.size() - 1)}; while(index < mx) { if(is_separator(strings[index])) { @@ -1470,7 +1480,11 @@ tuple_type_conversion(std::vector &strings, AssignTo &output) { } bool retval = lexical_conversion( std::vector(strings.begin(), strings.begin() + static_cast(index)), output); - strings.erase(strings.begin(), strings.begin() + static_cast(index) + 1); + if(strings.size() > index) { + strings.erase(strings.begin(), strings.begin() + static_cast(index) + 1); + } else { + strings.clear(); + } return retval; } diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 52e70363..4f7af6ad 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -66,20 +66,22 @@ target_include_directories( CLI11 ${SYSTEM_INCL} ${PUBLIC_OR_INTERFACE} $ $) -if(CMAKE_VERSION VERSION_LESS 3.8) - # This might not be a complete list - target_compile_features( - CLI11 - INTERFACE cxx_lambdas - cxx_nullptr - cxx_override - cxx_range_for - cxx_right_angle_brackets - cxx_strong_enums - cxx_constexpr - cxx_auto_type) -else() - target_compile_features(CLI11 INTERFACE cxx_std_11) +if(CMAKE_CXX_STANDARD LESS 14) + if(CMAKE_VERSION VERSION_LESS 3.8) + # This might not be a complete list + target_compile_features( + CLI11 + INTERFACE cxx_lambdas + cxx_nullptr + cxx_override + cxx_range_for + cxx_right_angle_brackets + cxx_strong_enums + cxx_constexpr + cxx_auto_type) + else() + target_compile_features(CLI11 INTERFACE cxx_std_11) + endif() endif() if(CLI11_SINGLE_FILE) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7c2bb7ed..4952e486 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -56,6 +56,10 @@ if(WIN32) list(APPEND CLI11_TESTS WindowsTest) endif() +if(CMAKE_CXX_STANDARD GREATER 16) + list(APPEND CLI11_TESTS FuzzFailTest) +endif() + if(Boost_FOUND) list(APPEND CLI11_TESTS BoostOptionTypeTest) endif() @@ -169,6 +173,16 @@ foreach(T IN LISTS CLI11_MULTIONLY_TESTS) add_catch_test(${T}) endforeach() +if(CMAKE_CXX_STANDARD GREATER 16) + set(TEST_FILE_FOLDER ${CMAKE_CURRENT_SOURCE_DIR}) + target_compile_definitions(FuzzFailTest PUBLIC -DTEST_FILE_FOLDER="${TEST_FILE_FOLDER}") + target_sources(FuzzFailTest PUBLIC ${PROJECT_SOURCE_DIR}/fuzz/fuzzApp.cpp) + if(CLI11_SINGLE_FILE AND CLI11_SINGLE_FILE_TESTS) + target_compile_definitions(FuzzFailTest_Single PUBLIC -DTEST_FILE_FOLDER="${TEST_FILE_FOLDER}") + target_sources(FuzzFailTest_Single PUBLIC ${PROJECT_SOURCE_DIR}/fuzz/fuzzApp.cpp) + endif() +endif() + # Add -Wno-deprecated-declarations to DeprecatedTest set(no-deprecated-declarations $<$:/wd4996> $<$>:-Wno-deprecated-declarations>) diff --git a/tests/FuzzFailTest.cpp b/tests/FuzzFailTest.cpp new file mode 100644 index 00000000..22148368 --- /dev/null +++ b/tests/FuzzFailTest.cpp @@ -0,0 +1,37 @@ +// Copyright (c) 2017-2023, University of Cincinnati, developed by Henry Schreiner +// under NSF AWARD 1414736 and by the respective contributors. +// All rights reserved. +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "../fuzz/fuzzApp.hpp" +#include "app_helper.hpp" + +std::string loadFailureFile(const std::string &type, int index) { + std::string fileName(TEST_FILE_FOLDER "/fuzzFail/"); + fileName.append(type); + fileName += std::to_string(index); + std::ifstream crashFile(fileName, std::ios::in | std::ios::binary); + if(crashFile) { + std::vector buffer(std::istreambuf_iterator(crashFile), {}); + + std::string cdata(buffer.begin(), buffer.end()); + return cdata; + } + return std::string{}; +} + +TEST_CASE("app_fail") { + CLI::FuzzApp fuzzdata; + + auto app = fuzzdata.generateApp(); + + int index = GENERATE(range(1, 3)); + + auto parseData = loadFailureFile("fuzz_app_fail", index); + try { + + app->parse(parseData); + } catch(const CLI::ParseError & /*e*/) { + } +} diff --git a/tests/NewParseTest.cpp b/tests/NewParseTest.cpp index ced35e69..503d0290 100644 --- a/tests/NewParseTest.cpp +++ b/tests/NewParseTest.cpp @@ -305,6 +305,27 @@ template class objWrapper { X val_{}; }; +/// simple class to wrap another with a very specific type constructor and assignment operators to test out some of the +/// option assignments +template class objWrapperRestricted { + public: + objWrapperRestricted() = default; + explicit objWrapperRestricted(int val) : val_{val} {}; + objWrapperRestricted(const objWrapperRestricted &) = delete; + objWrapperRestricted(objWrapperRestricted &&) = delete; + objWrapperRestricted &operator=(const objWrapperRestricted &) = delete; + objWrapperRestricted &operator=(objWrapperRestricted &&) = delete; + + objWrapperRestricted &operator=(int val) { + val_ = val; + return *this; + } + CLI11_NODISCARD const X &value() const { return val_; } + + private: + X val_{}; +}; + // I think there is a bug with the is_assignable in visual studio 2015 it is fixed in later versions // so this test will not compile in that compiler #if !defined(_MSC_VER) || _MSC_VER >= 1910 @@ -347,6 +368,26 @@ TEST_CASE_METHOD(TApp, "doubleWrapper", "[newparse]") { CHECK_THROWS_AS(run(), CLI::ConversionError); } +TEST_CASE_METHOD(TApp, "intWrapperRestricted", "[newparse]") { + objWrapperRestricted dWrapper; + app.add_option("-v", dWrapper); + args = {"-v", "4"}; + + run(); + + CHECK(4.0 == dWrapper.value()); + + args = {"-v", "thing"}; + + CHECK_THROWS_AS(run(), CLI::ConversionError); + + args = {"-v", ""}; + + run(); + + CHECK(0.0 == dWrapper.value()); +} + static_assert(CLI::detail::is_direct_constructible, int>::value, "int wrapper is not constructible from int64"); diff --git a/tests/OptionTypeTest.cpp b/tests/OptionTypeTest.cpp index 3acdde49..6d06a5af 100644 --- a/tests/OptionTypeTest.cpp +++ b/tests/OptionTypeTest.cpp @@ -336,6 +336,36 @@ TEST_CASE_METHOD(TApp, "pair_check", "[optiontype]") { CHECK_THROWS_AS(run(), CLI::ValidationError); } +TEST_CASE_METHOD(TApp, "pair_check_string", "[optiontype]") { + std::string myfile{"pair_check_file.txt"}; + bool ok = static_cast(std::ofstream(myfile.c_str()).put('a')); // create file + CHECK(ok); + + CHECK(CLI::ExistingFile(myfile).empty()); + std::pair findex; + + auto v0 = CLI::ExistingFile; + v0.application_index(0); + auto v1 = CLI::PositiveNumber; + v1.application_index(1); + app.add_option("--file", findex)->check(v0)->check(v1); + + args = {"--file", myfile, "2"}; + + CHECK_NOTHROW(run()); + + CHECK(myfile == findex.first); + CHECK("2" == findex.second); + + args = {"--file", myfile, "-3"}; + + CHECK_THROWS_AS(run(), CLI::ValidationError); + + args = {"--file", myfile, "2"}; + std::remove(myfile.c_str()); + CHECK_THROWS_AS(run(), CLI::ValidationError); +} + TEST_CASE_METHOD(TApp, "pair_check_take_first", "[optiontype]") { std::string myfile{"pair_check_file2.txt"}; bool ok = static_cast(std::ofstream(myfile.c_str()).put('a')); // create file diff --git a/tests/fuzzFail/fuzz_app_fail1 b/tests/fuzzFail/fuzz_app_fail1 new file mode 100644 index 00000000..d133524b Binary files /dev/null and b/tests/fuzzFail/fuzz_app_fail1 differ diff --git a/tests/fuzzFail/fuzz_app_fail2 b/tests/fuzzFail/fuzz_app_fail2 new file mode 100644 index 00000000..ac301930 --- /dev/null +++ b/tests/fuzzFail/fuzz_app_fail2 @@ -0,0 +1 @@ +1 c e g0 g0 æß --tup4 N3CLI10ParseErrorE% 0  %% 0 iÿÿÿÿÿÿÿÿÿÿÿÿÿÿwrap $   \ No newline at end of file