diff --git a/beast/module/core/text/LexicalCast.cpp b/beast/module/core/text/LexicalCast.cpp index a72b096172..bc0a172469 100644 --- a/beast/module/core/text/LexicalCast.cpp +++ b/beast/module/core/text/LexicalCast.cpp @@ -74,7 +74,6 @@ public: try { lexicalCastThrow("\xef\xbc\x91\xef\xbc\x90"); // utf-8 encoded - fail("Should throw"); } catch(BadLexicalCast const&) { @@ -82,6 +81,175 @@ public: } } + template + void tryBadConvert (std::string const& s) + { + T out; + expect (!lexicalCastChecked (out, s), s); + } + + void testConversionOverflows() + { + testcase ("conversion overflows"); + + tryBadConvert ("99999999999999999999"); + tryBadConvert ("4294967300"); + tryBadConvert ("75821"); + } + + void testConversionUnderflows () + { + testcase ("conversion underflows"); + + tryBadConvert ("-1"); + + tryBadConvert ("-99999999999999999999"); + tryBadConvert ("-4294967300"); + tryBadConvert ("-75821"); + } + + template + bool tryEdgeCase (std::string const& s) + { + T ret; + + bool const result = lexicalCastChecked (ret, s); + + if (!result) + return false; + + return s == std::to_string (ret); + } + + void testEdgeCases () + { + testcase ("conversion edge cases"); + + expect(tryEdgeCase ("18446744073709551614")); + expect(tryEdgeCase ("18446744073709551615")); + expect(!tryEdgeCase ("18446744073709551616")); + + expect(tryEdgeCase ("9223372036854775806")); + expect(tryEdgeCase ("9223372036854775807")); + expect(!tryEdgeCase ("9223372036854775808")); + + expect(tryEdgeCase ("-9223372036854775807")); + expect(tryEdgeCase ("-9223372036854775808")); + expect(!tryEdgeCase ("-9223372036854775809")); + + expect(tryEdgeCase ("4294967294")); + expect(tryEdgeCase ("4294967295")); + expect(!tryEdgeCase ("4294967296")); + + expect(tryEdgeCase ("2147483646")); + expect(tryEdgeCase ("2147483647")); + expect(!tryEdgeCase ("2147483648")); + + expect(tryEdgeCase ("-2147483647")); + expect(tryEdgeCase ("-2147483648")); + expect(!tryEdgeCase ("-2147483649")); + + expect(tryEdgeCase ("65534")); + expect(tryEdgeCase ("65535")); + expect(!tryEdgeCase ("65536")); + + expect(tryEdgeCase ("32766")); + expect(tryEdgeCase ("32767")); + expect(!tryEdgeCase ("32768")); + + expect(tryEdgeCase ("-32767")); + expect(tryEdgeCase ("-32768")); + expect(!tryEdgeCase ("-32769")); + } + + template + void testThrowConvert(std::string const& s, bool success) + { + bool result = !success; + T out; + + try + { + out = lexicalCastThrow (s); + result = true; + } + catch(BadLexicalCast const&) + { + result = false; + } + + expect (result == success, s); + } + + void testThrowingConversions () + { + testcase ("throwing conversion"); + + testThrowConvert ("99999999999999999999", false); + testThrowConvert ("9223372036854775806", true); + + testThrowConvert ("4294967290", true); + testThrowConvert ("42949672900", false); + testThrowConvert ("429496729000", false); + testThrowConvert ("4294967290000", false); + + testThrowConvert ("5294967295", false); + testThrowConvert ("-2147483644", true); + + testThrowConvert ("66666", false); + testThrowConvert ("-5711", true); + } + + void testZero () + { + testcase ("zero conversion"); + + { + std::int32_t out; + + expect (lexicalCastChecked (out, "-0"), "0"); + expect (lexicalCastChecked (out, "0"), "0"); + expect (lexicalCastChecked (out, "+0"), "0"); + } + + { + std::uint32_t out; + + expect (!lexicalCastChecked (out, "-0"), "0"); + expect (lexicalCastChecked (out, "0"), "0"); + expect (lexicalCastChecked (out, "+0"), "0"); + } + } + + void testEntireRange () + { + testcase ("entire range"); + + std::int32_t i = std::numeric_limits::min(); + std::string const empty(""); + + while (i <= std::numeric_limits::max()) + { + std::int16_t j = static_cast(i); + + auto actual = std::to_string (j); + + auto result = lexicalCast (j, empty); + + expect (result == actual, actual + " (string to integer)"); + + if (result == actual) + { + auto number = lexicalCast (result); + + if (number != j) + expect (false, actual + " (integer to string)"); + } + + i++; + } + } + void run() { std::int64_t const seedValue = 50; @@ -98,6 +266,12 @@ public: testIntegers (r); testPathologies(); + testConversionOverflows (); + testConversionUnderflows (); + testThrowingConversions (); + testZero (); + testEdgeCases (); + testEntireRange (); } }; diff --git a/beast/module/core/text/LexicalCast.h b/beast/module/core/text/LexicalCast.h index 926b768243..26649dc02f 100644 --- a/beast/module/core/text/LexicalCast.h +++ b/beast/module/core/text/LexicalCast.h @@ -29,6 +29,8 @@ #include #include +#include + namespace beast { namespace detail { @@ -47,14 +49,16 @@ parse_integral (Int& num, FwdIt first, FwdIt last, Accumulator accumulator) if (first == last) return false; + while (first != last) { auto const c = *first++; if (c < '0' || c > '9') return false; - if (!accumulator(Int(c - '0'))) + if (!accumulator(num, Int(c - '0'))) return false; } + return true; } @@ -62,14 +66,19 @@ template bool parse_negative_integral (Int& num, FwdIt first, FwdIt last) { - Int const limit = std::numeric_limits ::min(); + Int limit_value = std::numeric_limits ::min() / 10; + Int limit_digit = std::numeric_limits ::min() % 10; - return parse_integral (num, first, last, - [&num,&limit](Int n) + if (limit_digit < 0) + limit_digit = -limit_digit; + + return parse_integral (num, first, last, + [limit_value, limit_digit](Int& value, Int digit) { - if ((limit - (10 * num)) > -n) + assert ((digit >= 0) && (digit <= 9)); + if (value < limit_value || (value == limit_value && digit > limit_digit)) return false; - num = 10 * num - n; + value = (value * 10) - digit; return true; }); } @@ -78,14 +87,16 @@ template bool parse_positive_integral (Int& num, FwdIt first, FwdIt last) { - Int const limit = std::numeric_limits ::max(); + Int limit_value = std::numeric_limits ::max() / 10; + Int limit_digit = std::numeric_limits ::max() % 10; return parse_integral (num, first, last, - [&num,&limit](Int n) + [limit_value, limit_digit](Int& value, Int digit) { - if (n > (limit - (10 * num))) + assert ((digit >= 0) && (digit <= 9)); + if (value > limit_value || (value == limit_value && digit > limit_digit)) return false; - num = 10 * num + n; + value = (value * 10) + digit; return true; }); } @@ -129,17 +140,22 @@ struct LexicalCast; template struct LexicalCast { - bool operator() (std::string& out, short in) { out = std::to_string(in); return true; } - bool operator() (std::string& out, int in) { out = std::to_string(in); return true; } - bool operator() (std::string& out, long in) { out = std::to_string(in); return true; } - bool operator() (std::string& out, long long in) { out = std::to_string(in); return true; } - bool operator() (std::string& out, unsigned short in) { out = std::to_string(in); return true; } - bool operator() (std::string& out, unsigned int in) { out = std::to_string(in); return true; } - bool operator() (std::string& out, unsigned long in) { out = std::to_string(in); return true; } - bool operator() (std::string& out, unsigned long long in) { out = std::to_string(in); return true; } - bool operator() (std::string& out, float in) { out = std::to_string(in); return true; } - bool operator() (std::string& out, double in) { out = std::to_string(in); return true; } - bool operator() (std::string& out, long double in) { out = std::to_string(in); return true; } + template + std::enable_if_t ::value, bool> + operator () (std::string& out, Arithmetic in) + { + out = std::to_string (in); + return true; + } + + template + std::enable_if_t ::value, bool> + operator () (std::string& out, Enumeration in) + { + out = std::to_string ( + static_cast > (in)); + return true; + } }; // Parse std::string to number @@ -150,43 +166,20 @@ struct LexicalCast "beast::LexicalCast can only be used with integral types"); template - typename std::enable_if ::value, bool>::type + std::enable_if_t ::value, bool> operator () (Integral& out, std::string const& in) const { return parseUnsigned (out, std::begin(in), std::end(in)); } template - typename std::enable_if ::value, bool>::type + std::enable_if_t ::value, bool> operator () (Integral& out, std::string const& in) const { return parseSigned (out, std::begin(in), std::end(in)); } }; -#if 0 -template -bool -LexicalCast ::operator() (bool& out, std::string const& in) const -{ - // boost::lexical_cast is very strict, it - // throws on anything but "1" or "0" - // - if (in == "1") - { - out = true; - return true; - } - else if (in == "0") - { - out = false; - return true; - } - - return false; -} -#endif - //------------------------------------------------------------------------------ // Conversion from null terminated char const* @@ -206,15 +199,7 @@ struct LexicalCast { bool operator() (Out& out, char* in) const { - Out result; - - if (LexicalCast () (result, in)) - { - out = result; - return true; - } - - return false; + return LexicalCast ()(out, in); } };