1
0
mirror of https://github.com/CLIUtils/CLI11.git synced 2025-04-29 12:13:52 +00:00

Add check if lexical_cast fully consumed input on integer conversion (#68)

* Split signed/unsigned parsing, add check if input is fully consumed, improve tests

* Fix complex numbers parsing

* Add check if input is fully consumed for double lexical_cast, improve tests

* Add non-num non-string lexical_cast for parsable types, add tests

* Style fixes from check-style

* Some small fixes for corner case tests
This commit is contained in:
Anton 2018-03-09 15:01:19 +03:00 committed by Henry Schreiner
parent f089255ad0
commit 15f9a4f128
3 changed files with 86 additions and 11 deletions

View File

@ -575,6 +575,8 @@ class App {
std::string simple_name = CLI::detail::split(name, ',').at(0);
CLI::callback_t fun = [&variable, simple_name, label](results_t res) {
if(res[1].back() == 'i')
res[1].pop_back();
double x, y;
bool worked = detail::lexical_cast(res[0], x) && detail::lexical_cast(res[1], y);
if(worked)

View File

@ -72,13 +72,33 @@ constexpr const char *type_name() {
// Lexical cast
/// Integers / enums
/// Signed integers / enums
template <typename T,
enable_if_t<std::is_integral<T>::value || std::is_enum<T>::value, detail::enabler> = detail::dummy>
enable_if_t<(std::is_integral<T>::value && std::is_signed<T>::value) || std::is_enum<T>::value,
detail::enabler> = detail::dummy>
bool lexical_cast(std::string input, T &output) {
try {
output = static_cast<T>(std::stoll(input));
return true;
size_t n = 0;
output = static_cast<T>(std::stoll(input, &n, 0));
return n == input.size();
} catch(const std::invalid_argument &) {
return false;
} catch(const std::out_of_range &) {
return false;
}
}
/// Unsigned integers
template <typename T,
enable_if_t<std::is_integral<T>::value && std::is_unsigned<T>::value, detail::enabler> = detail::dummy>
bool lexical_cast(std::string input, T &output) {
if(!input.empty() && input.front() == '-')
return false; // std::stoull happily converts negative values to junk without any errors.
try {
size_t n = 0;
output = static_cast<T>(std::stoull(input, &n, 0));
return n == input.size();
} catch(const std::invalid_argument &) {
return false;
} catch(const std::out_of_range &) {
@ -90,8 +110,9 @@ bool lexical_cast(std::string input, T &output) {
template <typename T, enable_if_t<std::is_floating_point<T>::value, detail::enabler> = detail::dummy>
bool lexical_cast(std::string input, T &output) {
try {
output = static_cast<T>(std::stold(input));
return true;
size_t n = 0;
output = static_cast<T>(std::stold(input, &n));
return n == input.size();
} catch(const std::invalid_argument &) {
return false;
} catch(const std::out_of_range &) {
@ -101,12 +122,33 @@ bool lexical_cast(std::string input, T &output) {
/// String and similar
template <typename T,
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !std::is_enum<T>::value,
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !std::is_enum<T>::value &&
std::is_assignable<T &, std::string>::value,
detail::enabler> = detail::dummy>
bool lexical_cast(std::string input, T &output) {
output = input;
return true;
}
/// Non-string parsable
template <typename T,
enable_if_t<!std::is_floating_point<T>::value && !std::is_integral<T>::value && !std::is_enum<T>::value &&
!std::is_assignable<T &, std::string>::value,
detail::enabler> = detail::dummy>
bool lexical_cast(std::string input, T &output) {
// On GCC 4.7, thread_local is not available, so this optimization
// is turned off (avoiding multiple initialisations on multiple usages
#if defined(__GNUC__) && !defined(__clang__) && !defined(__INTEL_COMPILER) && __GNUC__ == 4 && (__GNUC_MINOR__ < 8)
std::istringstream is;
#else
static thread_local std::istringstream is;
#endif
is.str(input);
is >> output;
return !is.fail() && !is.rdbuf()->in_avail();
}
} // namespace detail
} // namespace CLI

View File

@ -4,6 +4,7 @@
#include <fstream>
#include <cstdint>
#include <string>
#include <complex>
TEST(Split, SimpleByToken) {
auto out = CLI::detail::split("one.two.three", '.');
@ -348,17 +349,30 @@ TEST(Types, TypeName) {
}
TEST(Types, LexicalCastInt) {
std::string input = "912";
int x;
EXPECT_TRUE(CLI::detail::lexical_cast(input, x));
EXPECT_EQ(912, x);
std::string signed_input = "-912";
int x_signed;
EXPECT_TRUE(CLI::detail::lexical_cast(signed_input, x_signed));
EXPECT_EQ(-912, x_signed);
std::string unsigned_input = "912";
unsigned int x_unsigned;
EXPECT_TRUE(CLI::detail::lexical_cast(unsigned_input, x_unsigned));
EXPECT_EQ(912, x_unsigned);
EXPECT_FALSE(CLI::detail::lexical_cast(signed_input, x_unsigned));
unsigned char y;
std::string overflow_input = std::to_string(UINT64_MAX) + "0";
EXPECT_FALSE(CLI::detail::lexical_cast(overflow_input, y));
char y_signed;
EXPECT_FALSE(CLI::detail::lexical_cast(overflow_input, y_signed));
std::string bad_input = "hello";
EXPECT_FALSE(CLI::detail::lexical_cast(bad_input, y));
std::string extra_input = "912i";
EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, y));
}
TEST(Types, LexicalCastDouble) {
@ -372,6 +386,9 @@ TEST(Types, LexicalCastDouble) {
std::string overflow_input = "1" + std::to_string(LDBL_MAX);
EXPECT_FALSE(CLI::detail::lexical_cast(overflow_input, x));
std::string extra_input = "9.12i";
EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, x));
}
TEST(Types, LexicalCastString) {
@ -381,6 +398,20 @@ TEST(Types, LexicalCastString) {
EXPECT_EQ(input, output);
}
TEST(Types, LexicalCastParsable) {
std::string input = "(4.2,7.3)";
std::string fail_input = "4.2,7.3";
std::string extra_input = "(4.2,7.3)e";
std::complex<double> output;
EXPECT_TRUE(CLI::detail::lexical_cast(input, output));
EXPECT_EQ(output.real(), 4.2); // Doing this in one go sometimes has trouble
EXPECT_EQ(output.imag(), 7.3); // on clang + c++4.8 due to missing const
EXPECT_FALSE(CLI::detail::lexical_cast(fail_input, output));
EXPECT_FALSE(CLI::detail::lexical_cast(extra_input, output));
}
TEST(FixNewLines, BasicCheck) {
std::string input = "one\ntwo";
std::string output = "one\n; two";