From 0358aac8ffec84b4f32b6835e6a456b0bc2eb78b Mon Sep 17 00:00:00 2001 From: domsoll <76645975+domsoll@users.noreply.github.com> Date: Thu, 31 Dec 2020 12:49:59 +0100 Subject: [PATCH] ENH: option to enable strict container size checks added, default is disabled ENH: test cases adapted for strict size checks option MNT: updated amalgamation header --- CMakeLists.txt | 2 + .../nlohmann/detail/conversions/from_json.hpp | 8 +++ include/nlohmann/detail/macro_scope.hpp | 4 ++ single_include/nlohmann/json.hpp | 12 ++++ test/src/unit-constructor1.cpp | 34 +++++++++ test/src/unit-conversions.cpp | 70 +++++++++++++++++-- 6 files changed, 126 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fa77a5aed..7e0f3b875 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,7 @@ option(JSON_BuildTests "Build the unit tests when BUILD_TESTING is enabled." ON) option(JSON_Install "Install CMake targets during install step." ON) option(JSON_MultipleHeaders "Use non-amalgamated version of the library." OFF) option(JSON_ImplicitConversions "Enable implicit conversions." ON) +option(JSON_StrictContainerSize "Enable strict size checks for container conversions." OFF) ## ## CONFIGURATION @@ -70,6 +71,7 @@ target_compile_definitions( ${NLOHMANN_JSON_TARGET_NAME} INTERFACE JSON_USE_IMPLICIT_CONVERSIONS=$ + JSON_USE_STRICT_CONTAINER_SIZE=$ ) target_include_directories( diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index b85a4e3d6..36fd72681 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -164,6 +164,7 @@ template auto from_json(const BasicJsonType& j, T (&arr)[N]) -> decltype(j.template get(), void()) { +#if JSON_USE_STRICT_CONTAINER_SIZE if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); @@ -172,6 +173,7 @@ auto from_json(const BasicJsonType& j, T (&arr)[N]) { JSON_THROW(type_error::create(302, "array size must be " + std::to_string(N) + ", but is " + std::to_string(j.size()))); } +#endif for (std::size_t i = 0; i < N; ++i) { arr[i] = j.at(i).template get(); @@ -189,11 +191,13 @@ auto from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) -> decltype(j.template get(), void()) { +#if JSON_USE_STRICT_CONTAINER_SIZE // array type check done in from_json already, only check size if (JSON_HEDLEY_UNLIKELY(j.size() != N)) { JSON_THROW(type_error::create(302, "array size must be " + std::to_string(N) + ", but is " + std::to_string(j.size()))); } +#endif for (std::size_t i = 0; i < N; ++i) { arr[i] = j.at(i).template get(); @@ -339,6 +343,7 @@ void from_json(const BasicJsonType& j, ArithmeticType& val) template void from_json(const BasicJsonType& j, std::pair& p) { +#if JSON_USE_STRICT_CONTAINER_SIZE if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); @@ -347,12 +352,14 @@ void from_json(const BasicJsonType& j, std::pair& p) { JSON_THROW(type_error::create(302, "array size must be 2, but is " + std::to_string(j.size()))); } +#endif p = {j.at(0).template get(), j.at(1).template get()}; } template void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence /*unused*/) { +#if JSON_USE_STRICT_CONTAINER_SIZE if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); @@ -361,6 +368,7 @@ void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence::type>()...); } diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 8c9f63296..ee22f1fd7 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -294,3 +294,7 @@ #else #define JSON_EXPLICIT explicit #endif + +#ifndef JSON_USE_STRICT_CONTAINER_SIZE + #define JSON_USE_STRICT_CONTAINER_SIZE 0 +#endif diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 8db2a0eff..ce0e59a2b 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -2312,6 +2312,10 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define JSON_EXPLICIT explicit #endif +#ifndef JSON_USE_STRICT_CONTAINER_SIZE + #define JSON_USE_STRICT_CONTAINER_SIZE 0 +#endif + namespace nlohmann { @@ -3564,6 +3568,7 @@ template auto from_json(const BasicJsonType& j, T (&arr)[N]) -> decltype(j.template get(), void()) { +#if JSON_USE_STRICT_CONTAINER_SIZE if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); @@ -3572,6 +3577,7 @@ auto from_json(const BasicJsonType& j, T (&arr)[N]) { JSON_THROW(type_error::create(302, "array size must be " + std::to_string(N) + ", but is " + std::to_string(j.size()))); } +#endif for (std::size_t i = 0; i < N; ++i) { arr[i] = j.at(i).template get(); @@ -3589,11 +3595,13 @@ auto from_json_array_impl(const BasicJsonType& j, std::array& arr, priority_tag<2> /*unused*/) -> decltype(j.template get(), void()) { +#if JSON_USE_STRICT_CONTAINER_SIZE // array type check done in from_json already, only check size if (JSON_HEDLEY_UNLIKELY(j.size() != N)) { JSON_THROW(type_error::create(302, "array size must be " + std::to_string(N) + ", but is " + std::to_string(j.size()))); } +#endif for (std::size_t i = 0; i < N; ++i) { arr[i] = j.at(i).template get(); @@ -3739,6 +3747,7 @@ void from_json(const BasicJsonType& j, ArithmeticType& val) template void from_json(const BasicJsonType& j, std::pair& p) { +#if JSON_USE_STRICT_CONTAINER_SIZE if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); @@ -3747,12 +3756,14 @@ void from_json(const BasicJsonType& j, std::pair& p) { JSON_THROW(type_error::create(302, "array size must be 2, but is " + std::to_string(j.size()))); } +#endif p = {j.at(0).template get(), j.at(1).template get()}; } template void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence /*unused*/) { +#if JSON_USE_STRICT_CONTAINER_SIZE if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { JSON_THROW(type_error::create(302, "type must be array, but is " + std::string(j.type_name()))); @@ -3761,6 +3772,7 @@ void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence::type>()...); } diff --git a/test/src/unit-constructor1.cpp b/test/src/unit-constructor1.cpp index b916a313c..2b958f0fc 100644 --- a/test/src/unit-constructor1.cpp +++ b/test/src/unit-constructor1.cpp @@ -269,6 +269,17 @@ TEST_CASE("constructors") CHECK(j[1] == std::get<1>(p)); } +#if !(JSON_USE_STRICT_CONTAINER_SIZE) + SECTION("std::pair with discarded values") + { + json j{1, 2.0, "string"}; + + const auto p = j.get>(); + CHECK(p.first == j[0]); + CHECK(p.second == j[1]); + } +#endif + SECTION("std::tuple") { const auto t = std::make_tuple(1.0, std::string{"string"}, 42, std::vector {0, 1}); @@ -284,8 +295,21 @@ TEST_CASE("constructors") CHECK(j[3][1] == 1); } +#if !(JSON_USE_STRICT_CONTAINER_SIZE) + SECTION("std::tuple with discarded values") + { + json j{1, 2.0, "string", 42}; + + const auto t = j.get>(); + CHECK(std::get<0>(t) == j[0]); + CHECK(std::get<1>(t) == j[1]); + CHECK(std::get<2>(t) == j[2]); + } +#endif + SECTION("std::pair/tuple/array failures") { +#if JSON_USE_STRICT_CONTAINER_SIZE json j1{1}; json j2{1, 2, 3}; @@ -301,6 +325,16 @@ TEST_CASE("constructors") CHECK_THROWS_WITH((j1.get>()), "[json.exception.type_error.302] array size must be 3, but is 1"); CHECK_THROWS_AS((j2.get>()), json::type_error&); CHECK_THROWS_WITH((j2.get>()), "[json.exception.type_error.302] array size must be 1, but is 3"); +#else + json j{1}; + + CHECK_THROWS_AS((j.get>()), json::out_of_range&); + CHECK_THROWS_WITH((j.get>()), "[json.exception.out_of_range.401] array index 1 is out of range"); + CHECK_THROWS_AS((j.get>()), json::out_of_range&); + CHECK_THROWS_WITH((j.get>()), "[json.exception.out_of_range.401] array index 1 is out of range"); + CHECK_THROWS_AS((j.get>()), json::out_of_range&); + CHECK_THROWS_WITH((j.get>()), "[json.exception.out_of_range.401] array index 1 is out of range"); +#endif } SECTION("std::forward_list") diff --git a/test/src/unit-conversions.cpp b/test/src/unit-conversions.cpp index 48b410fe7..4c2122294 100644 --- a/test/src/unit-conversions.cpp +++ b/test/src/unit-conversions.cpp @@ -405,18 +405,30 @@ TEST_CASE("value conversion") { json j1 = {1, 2, 3, 4}; int arr6[] = {1, 2, 3, 4, 5, 6}; +#if JSON_USE_STRICT_CONTAINER_SIZE CHECK_THROWS_AS(j1.get_to(arr6), json::type_error&); CHECK_THROWS_WITH(j1.get_to(arr6), "[json.exception.type_error.302] " "array size must be 6, but is 4"); +#else + CHECK_THROWS_AS(j1.get_to(arr6), json::out_of_range&); + CHECK_THROWS_WITH(j1.get_to(arr6), "[json.exception.out_of_range.401] " + "array index 4 is out of range"); +#endif } SECTION("built-in array is smaller than JSON") { json j1 = {1, 2, 3, 4}; int arr2[] = {8, 9}; +#if JSON_USE_STRICT_CONTAINER_SIZE CHECK_THROWS_AS(j1.get_to(arr2), json::type_error&); CHECK_THROWS_WITH(j1.get_to(arr2), "[json.exception.type_error.302] " "array size must be 2, but is 4"); +#else + j1.get_to(arr2); + CHECK(arr2[0] == 1); + CHECK(arr2[1] == 2); +#endif } SECTION("built-in array from non-array type") @@ -424,8 +436,13 @@ TEST_CASE("value conversion") json j1; int arr2[] = {8, 9}; CHECK_THROWS_AS(j1.get_to(arr2), json::type_error&); +#if JSON_USE_STRICT_CONTAINER_SIZE CHECK_THROWS_WITH(j1.get_to(arr2), "[json.exception.type_error.302] " "type must be array, but is null"); +#else + CHECK_THROWS_WITH(j1.get_to(arr2), "[json.exception.type_error.304] " + "cannot use at() with null"); +#endif } } @@ -1525,17 +1542,29 @@ TEST_CASE("value conversion") SECTION("std::array is larger than JSON") { std::array arr6 = {{1, 2, 3, 4, 5, 6}}; +#if JSON_USE_STRICT_CONTAINER_SIZE CHECK_THROWS_AS(j1.get_to(arr6), json::type_error&); CHECK_THROWS_WITH(j1.get_to(arr6), "[json.exception.type_error.302] " "array size must be 6, but is 4"); +#else + CHECK_THROWS_AS(j1.get_to(arr6), json::out_of_range&); + CHECK_THROWS_WITH(j1.get_to(arr6), "[json.exception.out_of_range.401] " + "array index 4 is out of range"); +#endif } SECTION("std::array is smaller than JSON") { std::array arr2 = {{8, 9}}; +#if JSON_USE_STRICT_CONTAINER_SIZE CHECK_THROWS_AS(j1.get_to(arr2), json::type_error&); CHECK_THROWS_WITH(j1.get_to(arr2), "[json.exception.type_error.302] " "array size must be 2, but is 4"); +#else + j1.get_to(arr2); + CHECK(arr2[0] == 1); + CHECK(arr2[1] == 2); +#endif } } @@ -1703,18 +1732,30 @@ TEST_CASE("value conversion") SECTION("std::pair is larger than JSON") { - std::pair p; + std::pair p(8, 9); +#if JSON_USE_STRICT_CONTAINER_SIZE CHECK_THROWS_AS(j10.get_to(p), json::type_error&); CHECK_THROWS_WITH(j10.get_to(p), "[json.exception.type_error.302] " "array size must be 2, but is 1"); +#else + CHECK_THROWS_AS(j10.get_to(p), json::out_of_range&); + CHECK_THROWS_WITH(j10.get_to(p), "[json.exception.out_of_range.401] " + "array index 1 is out of range"); +#endif } SECTION("std::pair is smaller than JSON") { - std::pair p; + std::pair p(8, 9); +#if JSON_USE_STRICT_CONTAINER_SIZE CHECK_THROWS_AS(j9.get_to(p), json::type_error&); CHECK_THROWS_WITH(j9.get_to(p), "[json.exception.type_error.302] " "array size must be 2, but is 3"); +#else + j9.get_to(p); + CHECK(p.first == 1); + CHECK(p.second == 2); +#endif } } @@ -1733,18 +1774,30 @@ TEST_CASE("value conversion") SECTION("std::tuple is larger than JSON") { - std::tuple t; + std::tuple t(8, 9); +#if JSON_USE_STRICT_CONTAINER_SIZE CHECK_THROWS_AS(j10.get_to(t), json::type_error&); CHECK_THROWS_WITH(j10.get_to(t), "[json.exception.type_error.302] " "array size must be 2, but is 1"); +#else + CHECK_THROWS_AS(j10.get_to(t), json::out_of_range&); + CHECK_THROWS_WITH(j10.get_to(t), "[json.exception.out_of_range.401] " + "array index 1 is out of range"); +#endif } SECTION("std::tuple is smaller than JSON") { - std::tuple t; + std::tuple t(8, 9); +#if JSON_USE_STRICT_CONTAINER_SIZE CHECK_THROWS_AS(j9.get_to(t), json::type_error&); CHECK_THROWS_WITH(j9.get_to(t), "[json.exception.type_error.302] " "array size must be 2, but is 3"); +#else + j9.get_to(t); + CHECK(std::get<0>(t) == 1); + CHECK(std::get<1>(t) == 2); +#endif } } @@ -1753,12 +1806,21 @@ TEST_CASE("value conversion") CHECK_THROWS_AS((json().get>()), json::type_error&); CHECK_THROWS_AS((json().get>()), json::type_error&); +#if JSON_USE_STRICT_CONTAINER_SIZE CHECK_THROWS_WITH( (json().get>()), "[json.exception.type_error.302] type must be array, but is null"); CHECK_THROWS_WITH( (json().get>()), "[json.exception.type_error.302] type must be array, but is null"); +#else + CHECK_THROWS_WITH( + (json().get>()), + "[json.exception.type_error.304] cannot use at() with null"); + CHECK_THROWS_WITH( + (json().get>()), + "[json.exception.type_error.304] cannot use at() with null"); +#endif } } }