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
This commit is contained in:
domsoll 2020-12-31 12:49:59 +01:00
parent 748f4ec227
commit 0358aac8ff
6 changed files with 126 additions and 4 deletions

View File

@ -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=$<BOOL:${JSON_ImplicitConversions}>
JSON_USE_STRICT_CONTAINER_SIZE=$<BOOL:${JSON_StrictContainerSize}>
)
target_include_directories(

View File

@ -164,6 +164,7 @@ template<typename BasicJsonType, typename T, std::size_t N>
auto from_json(const BasicJsonType& j, T (&arr)[N])
-> decltype(j.template get<T>(), 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<T>();
@ -189,11 +191,13 @@ auto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,
priority_tag<2> /*unused*/)
-> decltype(j.template get<T>(), 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<T>();
@ -339,6 +343,7 @@ void from_json(const BasicJsonType& j, ArithmeticType& val)
template<typename BasicJsonType, typename A1, typename A2>
void from_json(const BasicJsonType& j, std::pair<A1, A2>& 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<A1, A2>& 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<A1>(), j.at(1).template get<A2>()};
}
template<typename BasicJsonType, typename Tuple, std::size_t... Idx>
void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence<Idx...> /*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<Idx..
{
JSON_THROW(type_error::create(302, "array size must be " + std::to_string(sizeof...(Idx)) + ", but is " + std::to_string(j.size())));
}
#endif
t = std::make_tuple(j.at(Idx).template get<typename std::tuple_element<Idx, Tuple>::type>()...);
}

View File

@ -294,3 +294,7 @@
#else
#define JSON_EXPLICIT explicit
#endif
#ifndef JSON_USE_STRICT_CONTAINER_SIZE
#define JSON_USE_STRICT_CONTAINER_SIZE 0
#endif

View File

@ -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<typename BasicJsonType, typename T, std::size_t N>
auto from_json(const BasicJsonType& j, T (&arr)[N])
-> decltype(j.template get<T>(), 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<T>();
@ -3589,11 +3595,13 @@ auto from_json_array_impl(const BasicJsonType& j, std::array<T, N>& arr,
priority_tag<2> /*unused*/)
-> decltype(j.template get<T>(), 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<T>();
@ -3739,6 +3747,7 @@ void from_json(const BasicJsonType& j, ArithmeticType& val)
template<typename BasicJsonType, typename A1, typename A2>
void from_json(const BasicJsonType& j, std::pair<A1, A2>& 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<A1, A2>& 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<A1>(), j.at(1).template get<A2>()};
}
template<typename BasicJsonType, typename Tuple, std::size_t... Idx>
void from_json_tuple_impl(const BasicJsonType& j, Tuple& t, index_sequence<Idx...> /*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<Idx..
{
JSON_THROW(type_error::create(302, "array size must be " + std::to_string(sizeof...(Idx)) + ", but is " + std::to_string(j.size())));
}
#endif
t = std::make_tuple(j.at(Idx).template get<typename std::tuple_element<Idx, Tuple>::type>()...);
}

View File

@ -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<std::pair<int, float>>();
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<int> {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<std::tuple<int, float, std::string>>();
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<std::array<int, 3>>()), "[json.exception.type_error.302] array size must be 3, but is 1");
CHECK_THROWS_AS((j2.get<std::array<int, 1>>()), json::type_error&);
CHECK_THROWS_WITH((j2.get<std::array<int, 1>>()), "[json.exception.type_error.302] array size must be 1, but is 3");
#else
json j{1};
CHECK_THROWS_AS((j.get<std::pair<int, int>>()), json::out_of_range&);
CHECK_THROWS_WITH((j.get<std::pair<int, int>>()), "[json.exception.out_of_range.401] array index 1 is out of range");
CHECK_THROWS_AS((j.get<std::tuple<int, int>>()), json::out_of_range&);
CHECK_THROWS_WITH((j.get<std::tuple<int, int>>()), "[json.exception.out_of_range.401] array index 1 is out of range");
CHECK_THROWS_AS((j.get<std::array<int, 3>>()), json::out_of_range&);
CHECK_THROWS_WITH((j.get<std::array<int, 3>>()), "[json.exception.out_of_range.401] array index 1 is out of range");
#endif
}
SECTION("std::forward_list<json>")

View File

@ -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<int, 6> 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<int, 2> 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<int, int> p;
std::pair<int, int> 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<int, int> p;
std::pair<int, int> 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<int, int> t;
std::tuple<int, int> 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<int, int> t;
std::tuple<int, int> 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<std::pair<int, int>>()), json::type_error&);
CHECK_THROWS_AS((json().get<std::tuple<int, int>>()), json::type_error&);
#if JSON_USE_STRICT_CONTAINER_SIZE
CHECK_THROWS_WITH(
(json().get<std::pair<int, int>>()),
"[json.exception.type_error.302] type must be array, but is null");
CHECK_THROWS_WITH(
(json().get<std::tuple<int, int>>()),
"[json.exception.type_error.302] type must be array, but is null");
#else
CHECK_THROWS_WITH(
(json().get<std::pair<int, int>>()),
"[json.exception.type_error.304] cannot use at() with null");
CHECK_THROWS_WITH(
(json().get<std::tuple<int, int>>()),
"[json.exception.type_error.304] cannot use at() with null");
#endif
}
}
}