diff --git a/include/CLI/TypeTools.hpp b/include/CLI/TypeTools.hpp index 17997446d..0d2daef80 100644 --- a/include/CLI/TypeTools.hpp +++ b/include/CLI/TypeTools.hpp @@ -861,7 +861,7 @@ bool integral_conversion(const std::string &input, T &output) noexcept { if(input.empty() || input.front() == '-') { return false; } - char *val = nullptr; + char *val{nullptr}; errno = 0; std::uint64_t output_ll = std::strtoull(input.c_str(), &val, 0); if(errno == ERANGE) { @@ -904,8 +904,8 @@ bool integral_conversion(const std::string &input, T &output) noexcept { return false; } -/// Convert a flag into an integer value typically binary flags -inline std::int64_t to_flag_value(std::string val) { +/// Convert a flag into an integer value typically binary flags sets errno to nonzero if conversion failed +inline std::int64_t to_flag_value(std::string val) noexcept { static const std::string trueString("true"); static const std::string falseString("false"); if(val == trueString) { @@ -933,7 +933,8 @@ inline std::int64_t to_flag_value(std::string val) { ret = 1; break; default: - throw std::invalid_argument("unrecognized character"); + errno = EINVAL; + return -1; } return ret; } @@ -942,7 +943,11 @@ inline std::int64_t to_flag_value(std::string val) { } else if(val == falseString || val == "off" || val == "no" || val == "disable") { ret = -1; } else { - ret = std::stoll(val); + char *loc_ptr{nullptr}; + ret = std::strtoll(val.c_str(), &loc_ptr, 0); + if(loc_ptr != (val.c_str() + val.size()) && errno == 0) { + errno = EINVAL; + } } return ret; } @@ -971,18 +976,16 @@ bool lexical_cast(const std::string &input, T &output) { template ::value == object_category::boolean_value, detail::enabler> = detail::dummy> bool lexical_cast(const std::string &input, T &output) { - try { - auto out = to_flag_value(input); + errno = 0; + auto out = to_flag_value(input); + if(errno == 0) { output = (out > 0); - return true; - } catch(const std::invalid_argument &) { - return false; - } catch(const std::out_of_range &) { - // if the number is out of the range of a 64 bit value then it is still a number and for this purpose is still - // valid all we care about the sign + } else if(errno == ERANGE) { output = (input[0] != '-'); - return true; + } else { + return false; } + return true; } /// Floats @@ -1638,12 +1641,13 @@ inline std::string sum_string_vector(const std::vector &values) { double tv{0.0}; auto comp = lexical_cast(arg, tv); if(!comp) { - try { - tv = static_cast(detail::to_flag_value(arg)); - } catch(const std::exception &) { - fail = true; + errno = 0; + auto fv = detail::to_flag_value(arg); + fail = (errno != 0); + if(fail) { break; } + tv = static_cast(fv); } val += tv; } diff --git a/include/CLI/impl/App_inl.hpp b/include/CLI/impl/App_inl.hpp index fd8c375a5..5e703eddf 100644 --- a/include/CLI/impl/App_inl.hpp +++ b/include/CLI/impl/App_inl.hpp @@ -1431,18 +1431,15 @@ CLI11_INLINE bool App::_parse_single_config(const ConfigItem &item, std::size_t auto res = config_formatter_->to_flag(item); bool converted{false}; if(op->get_disable_flag_override()) { - - try { - auto val = detail::to_flag_value(res); - if(val == 1) { - res = op->get_flag_value(item.name, "{}"); - converted = true; - } - } catch(...) { + auto val = detail::to_flag_value(res); + if(val == 1) { + res = op->get_flag_value(item.name, "{}"); + converted = true; } } if(!converted) { + errno = 0; res = op->get_flag_value(item.name, res); } diff --git a/include/CLI/impl/Option_inl.hpp b/include/CLI/impl/Option_inl.hpp index a24df9ab2..250b626b6 100644 --- a/include/CLI/impl/Option_inl.hpp +++ b/include/CLI/impl/Option_inl.hpp @@ -389,15 +389,15 @@ CLI11_NODISCARD CLI11_INLINE std::string Option::get_flag_value(const std::strin return input_value; } if(default_flag_values_[static_cast(ind)].second == falseString) { - try { - auto val = detail::to_flag_value(input_value); - return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val)); - } catch(const std::invalid_argument &) { + errno = 0; + auto val = detail::to_flag_value(input_value); + if(errno != 0) { + errno = 0; return input_value; } - } else { - return input_value; + return (val == 1) ? falseString : (val == (-1) ? trueString : std::to_string(-val)); } + return input_value; } CLI11_INLINE Option *Option::add_result(std::string s) { diff --git a/tests/FuzzFailTest.cpp b/tests/FuzzFailTest.cpp index 8b2e30383..97965888c 100644 --- a/tests/FuzzFailTest.cpp +++ b/tests/FuzzFailTest.cpp @@ -45,3 +45,16 @@ TEST_CASE("app_fail") { } catch(const CLI::ConstructionError & /*e*/) { } } + +TEST_CASE("file_fail") { + CLI::FuzzApp fuzzdata; + auto app = fuzzdata.generateApp(); + + int index = GENERATE(range(1, 2)); + auto parseData = loadFailureFile("fuzz_file_fail", index); + std::stringstream out(parseData); + try { + app->parse_from_stream(out); + } catch(const CLI::ParseError & /*e*/) { + } +} diff --git a/tests/HelpersTest.cpp b/tests/HelpersTest.cpp index d92d3fcef..529cfa69f 100644 --- a/tests/HelpersTest.cpp +++ b/tests/HelpersTest.cpp @@ -201,15 +201,26 @@ TEST_CASE("StringTools: Modify3", "[helpers]") { } TEST_CASE("StringTools: flagValues", "[helpers]") { + errno = 0; CHECK(-1 == CLI::detail::to_flag_value("0")); + CHECK(errno == 0); CHECK(1 == CLI::detail::to_flag_value("t")); CHECK(1 == CLI::detail::to_flag_value("1")); CHECK(6 == CLI::detail::to_flag_value("6")); CHECK(-6 == CLI::detail::to_flag_value("-6")); CHECK(-1 == CLI::detail::to_flag_value("false")); CHECK(1 == CLI::detail::to_flag_value("YES")); - CHECK_THROWS_AS(CLI::detail::to_flag_value("frog"), std::invalid_argument); - CHECK_THROWS_AS(CLI::detail::to_flag_value("q"), std::invalid_argument); + errno = 0; + CLI::detail::to_flag_value("frog"); + CHECK(errno == EINVAL); + errno = 0; + CLI::detail::to_flag_value("q"); + CHECK(errno == EINVAL); + errno = 0; + CLI::detail::to_flag_value( + "77777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777777"); + CHECK(errno == ERANGE); + errno = 0; CHECK(-1 == CLI::detail::to_flag_value("NO")); CHECK(475555233 == CLI::detail::to_flag_value("475555233")); } diff --git a/tests/fuzzFail/fuzz_file_fail1 b/tests/fuzzFail/fuzz_file_fail1 new file mode 100644 index 000000000..06b1c382f --- /dev/null +++ b/tests/fuzzFail/fuzz_file_fail1 @@ -0,0 +1 @@ +nflag2=555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555555"="