diff --git a/iguana/detail/charconv.h b/iguana/detail/charconv.h index 7bf2e6a0..16b34be7 100644 --- a/iguana/detail/charconv.h +++ b/iguana/detail/charconv.h @@ -3,9 +3,9 @@ #include "dragonbox_to_chars.h" #include "fast_float.h" +#include "iguana/define.h" #include "itoa.hpp" - namespace iguana { template struct is_char_type @@ -13,17 +13,26 @@ struct is_char_type std::is_same, std::is_same> {}; namespace detail { -template + +// check_number==true: check if the string [first, last) is a legal number +template std::pair from_chars(const char *first, - const char *last, - U &value) noexcept { + const char *last, U &value) { using T = std::decay_t; if constexpr (std::is_floating_point_v) { auto [p, ec] = fast_float::from_chars(first, last, value); + if constexpr (check_number) { + if (p != last || ec != std::errc{}) + IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); } + } return {p, ec}; } else { auto [p, ec] = std::from_chars(first, last, value); + if constexpr (check_number) { + if (p != last || ec != std::errc{}) + IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); } + } return {p, ec}; } } diff --git a/iguana/json_reader.hpp b/iguana/json_reader.hpp index b41b19c8..1dcd9a58 100644 --- a/iguana/json_reader.hpp +++ b/iguana/json_reader.hpp @@ -68,8 +68,8 @@ IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) { if (size == 0) IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); } const auto start = &*it; - auto [p, ec] = detail::from_chars(start, start + size, value); - if (ec != std::errc{} || *p == '.') + auto [p, ec] = detail::from_chars(start, start + size, value); + if (ec != std::errc{} || !can_follow_number(*p)) IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); } it += (p - &*it); } @@ -82,9 +82,7 @@ IGUANA_INLINE void from_json_impl(U &value, It &&it, It &&end) { buffer[i] = *it++; ++i; } - auto [p, ec] = detail::from_chars(buffer, buffer + i, value); - if (ec != std::errc{}) - IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); } + detail::from_chars(buffer, buffer + i, value); } } diff --git a/iguana/json_util.hpp b/iguana/json_util.hpp index 5c00c077..f1bf3a33 100644 --- a/iguana/json_util.hpp +++ b/iguana/json_util.hpp @@ -18,10 +18,7 @@ class numeric_str { if (val_.empty()) IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); } T res; - auto [_, ec] = - detail::from_chars(val_.data(), val_.data() + val_.size(), res); - if (ec != std::errc{}) - IGUANA_UNLIKELY { throw std::runtime_error("Failed to parse number"); } + detail::from_chars(val_.data(), val_.data() + val_.size(), res); return res; } @@ -214,4 +211,28 @@ IGUANA_INLINE bool is_numeric(char c) noexcept { return static_cast(is_num[static_cast(c)]); } +// '\t' '\r' '\n' '"' '}' ']' ',' ' ' '\0' +IGUANA_INLINE bool can_follow_number(char c) noexcept { + static constexpr int can_follow_num[256] = { + // 0 1 2 3 4 5 6 7 8 9 A B C D E F + 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 0, 0, // 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 1 + 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, // 2 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 3 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 4 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, // 5 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 6 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, // 7 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // F + }; + return static_cast(can_follow_num[static_cast(c)]); +} + } // namespace iguana diff --git a/iguana/xml_reader.hpp b/iguana/xml_reader.hpp index bca3fe4b..d1a71294 100644 --- a/iguana/xml_reader.hpp +++ b/iguana/xml_reader.hpp @@ -5,7 +5,6 @@ #include "detail/utf.hpp" #include "xml_util.hpp" - namespace iguana { namespace detail { @@ -34,10 +33,7 @@ IGUANA_INLINE void parse_value(U &&value, It &&begin, It &&end) { else if constexpr (num_v) { auto size = std::distance(begin, end); const auto start = &*begin; - auto [p, ec] = detail::from_chars(start, start + size, value); - if (ec != std::errc{}) - IGUANA_UNLIKELY - throw std::runtime_error("Failed to parse number"); + detail::from_chars(start, start + size, value); } else if constexpr (char_v) { if (static_cast(std::distance(begin, end)) != 1) diff --git a/iguana/yaml_reader.hpp b/iguana/yaml_reader.hpp index c2987a6e..0a0d47fa 100644 --- a/iguana/yaml_reader.hpp +++ b/iguana/yaml_reader.hpp @@ -6,7 +6,6 @@ #include "detail/utf.hpp" #include "yaml_util.hpp" - namespace iguana { template , int> = 0> @@ -114,10 +113,7 @@ IGUANA_INLINE void parse_value(U &value, It &&value_begin, It &&value_end) { IGUANA_UNLIKELY { return; } auto size = std::distance(value_begin, value_end); const auto start = &*value_begin; - auto [p, ec] = detail::from_chars(start, start + size, value); - if (ec != std::errc{}) - IGUANA_UNLIKELY - throw std::runtime_error("Failed to parse number"); + detail::from_chars(start, start + size, value); } // string_view should be used for string with ' " ? diff --git a/test/test.cpp b/test/test.cpp index 3200ca9a..c3d75691 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -180,23 +180,22 @@ struct nest_t { }; REFLECTION(nest_t, name, value, var, var2); -struct point_t1 { - int x; - int y; -}; -REFLECTION(point_t1, x, y); - -TEST_CASE("test double to int") { - point_t p{1, 0.45}; - std::string s; - iguana::to_json(p, s); - std::cout << s << std::endl; - point_t1 p2; - CHECK_THROWS(iguana::from_json(p2, s)); - - point_t p3; - iguana::from_json(p3, s); - CHECK(p.y == p3.y); +TEST_CASE("test throw while parsing an illegal number") { + { + std::string str{"[0,1.0]"}; + std::vector test{}; + CHECK_THROWS(iguana::from_json(test, str.begin(), str.end())); + } + { + std::string str{"1A"}; + int test{}; + CHECK_THROWS(iguana::from_json(test, str.begin(), str.end())); + } + { + std::string str{"1.0"}; + int test{}; + CHECK_THROWS(iguana::from_json(test, str.begin(), str.end())); + } } TEST_CASE("test variant") { diff --git a/test/test_xml.cpp b/test/test_xml.cpp index 0d8a2830..cdd6c478 100644 --- a/test/test_xml.cpp +++ b/test/test_xml.cpp @@ -700,6 +700,31 @@ TEST_CASE("test smart_ptr") { validator(cont1); } +TEST_CASE("test throw while parsing an illegal number") { + { + std::string str = R"( + + 42A + 15 + test + + )"; + Contents_t cont; + CHECK_THROWS(iguana::from_xml(cont, str)); + } + { + std::string str = R"( + + 42 + 15.7 + test + + )"; + Contents_t cont; + CHECK_THROWS(iguana::from_xml(cont, str)); + } +} + struct next_obj_t { int x; int y; diff --git a/test/unit_test.cpp b/test/unit_test.cpp index 64dc3b09..ca58851d 100644 --- a/test/unit_test.cpp +++ b/test/unit_test.cpp @@ -206,7 +206,6 @@ TEST_CASE("test parse item seq container") { std::array test{}; CHECK_THROWS(iguana::from_json(test, str.begin(), str.end())); } - { std::string str{"[0,1,2"}; std::list test{};